The content format
When a manuscript arrives, the temptation is to keep it as a Word file, or an HTML page, or a Markdown document — a single blob shaped for one destination. The moment you do that, the content is stuck: to make an ebook you re-flow it, to make an audiobook you re-key it, to feed it to an AI assistant you scrape it back apart. Every format becomes a fresh copy, and the copies drift.
The Content Lake avoids that trap by storing content not as a blob but as structured blocks. A chapter is an ordered list of discrete pieces — a heading, a paragraph, a code listing, an image, a table — each held as data rather than as marked-up text. Because the meaning is captured (this is a heading; that is a link) rather than its appearance in one medium, a single stored source can be rendered faithfully to any target. One paragraph becomes a line of Markdown, a snippet of HTML, or a sentence a narrator reads aloud — all from the same underlying record. This is the format the Content Lake speaks: Packt Content Format (PCF).
Storing content this way pays off far beyond rendering. Because each block is addressable, edits can be diffed cleanly, search can chunk the content into meaningful pieces, and the Knowledge Graph can annotate a phrase by position without ever touching the words. And because PCF describes structure and semantics — never presentation — the platform holds content once and lets every downstream product, channel, and AI surface take the shape it needs.
This page explains the shape of PCF for a general reader: what blocks, spans, and marks are, how the same content renders four different ways, and how a block's stable name differs from its content fingerprint. The JSON here is illustrative. The exact, bit-for-bit rules that two independent implementations must agree on live in the platform PCF conformance specification, which is the authoritative source.
One source, rendered anywhere
The whole point of PCF is that the same stored content can be handed to different renderers and each produces the right thing for its medium. A renderer walks the blocks in order and decides what a heading or a link means where it is going: bold on a web page, a page number in print, a spoken cue in an audiobook.
flowchart LR
PCF["PCF<br/>(stored once<br/>as structured blocks)"]
PCF --> MD["Markdown"]
PCF --> HTML["HTML / web"]
PCF --> EPUB["ePub · PDF · print"]
PCF --> AUDIO["Audiobook<br/>narration"]
PCF --> AI["AI retrieval<br/>& search"]
The tabs below show one small fragment — a heading followed by a paragraph whose middle phrase is a link — in its stored PCF form and in three rendered outputs. Nothing about the content changes between tabs; only the renderer differs.
[
{
"_type": "block",
"_key": "intro-heading",
"style": "h1",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "Introduction",
"marks": []
}
]
},
{
"_type": "block",
"_key": "intro-para",
"style": "normal",
"markDefs": [
{
"_type": "link",
"_key": "packt-link",
"href": "https://www.packtpub.com"
}
],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "Introducing the ",
"marks": []
},
{
"_type": "span",
"_key": "s2",
"text": "Packt",
"marks": ["packt-link"]
},
{
"_type": "span",
"_key": "s3",
"text": " content format to enhance publishing flows and integrate context to content.",
"marks": []
}
]
}
]
Notice how the link survives as a real hyperlink in HTML and Markdown but is flattened to plain narration in the audiobook — the renderer decides what a link means in its medium. The heading becomes a spoken "Chapter one" cue rather than large text. This faithfulness across every medium is exactly what the structure-not-presentation design buys.
Where rendering happens
The Content Lake stores and serves PCF JSON only — the
highest-fidelity form. Rendered formats such as Markdown are produced
at the Public API edge through content
negotiation: a caller asks for text/markdown and the edge
serialises PCF into it on the way out. Markdown, HTML, ePub, and
narration are outputs, never something the Content Lake holds.
Blocks, spans, and marks
A PCF document is, at its root, an ordered array of blocks. Most blocks are text — a paragraph, a heading, a list item, a blockquote — but images, code listings, tables, formulae, and callouts are blocks too, sitting at the same level. Reordering, filtering, or transforming content is just array work.
Inside a text block, the words live in spans. A span is a run of text that shares the same formatting. When part of a sentence is bold or is a link, that part becomes its own span; the plain text around it stays in separate spans. Formatting is applied to a span through marks.
Marks come in two kinds. A decorator is a simple label like
strong (bold) or em (italic) — it needs no extra data, so it is just
a string in the span's marks list. An annotation carries
structured data — a link needs a URL, a footnote needs a body — so it is
defined once in the block's markDefs list under a key, and a span
opts in by naming that key in its marks.
flowchart TD
DOC["PCF document<br/>(ordered array of blocks)"]
DOC --> B1["block · heading"]
DOC --> B2["block · paragraph"]
DOC --> B3["image · code · table …"]
B2 --> MD["markDefs<br/>(link, footnote, …)"]
B2 --> SP1["span · 'Introducing the '"]
B2 --> SP2["span · 'Packt'"]
B2 --> SP3["span · ' content format…'"]
SP2 -->|"marks: strong + link"| MK["decorator 'strong'<br/>+ annotation → markDef"]
MK -.-> MD
The one-paragraph example in the tabs above shows all three at work: two
blocks (a heading and a paragraph), the paragraph split into three
spans, and a link annotation defined in markDefs that the middle
span references by its key. A single span can carry several marks at
once — a phrase can be both bold and a link by listing both.
It is a superset of Portable Text
PCF builds on the open Portable Text standard, so the block / span / markDefs model is familiar to a wide tooling ecosystem. PCF adds required stable keys, publishing-specific block types, and a separate entity layer — all additive. Any valid Portable Text document becomes valid PCF once keys are assigned.
The building blocks you will meet
Beyond plain text, PCF defines a small set of standard block types that cover almost everything in a technical book. Each is stored as data with its own fields, so a renderer can treat it appropriately — and a renderer that cannot handle one (a text-to-speech engine meeting a code listing, say) produces a sensible fallback rather than failing.
| Block type | What it holds |
|---|---|
block |
Text — paragraphs, headings (h1–h6), blockquotes, list items. |
image |
A picture with alt text, an optional rich caption, and asset references. |
video |
An embedded or hosted video with provider metadata and a caption. |
codeBlock |
A code listing with a language, an optional filename, and line highlighting. |
table |
Rows and columns of rich-text cells, with header and cell-spanning support. |
formula |
A mathematical expression as LaTeX, shown as a display equation or inline. |
aside |
A callout — tip, warning, danger, note — with a rich inner body. |
Building a parser or renderer?
This page is the friendly overview. For the complete developer reference — every block, span, mark, and custom type with its full JSON shape and field semantics — see PCF reference.
Text blocks can also carry annotations richer than a plain link:
footnotes and endnotes (each with a full rich-text body), structured
citations for a generated bibliography, cross-references to another
block ("see Figure 3.1"), and editorial comments that never render in
published output. Lists need no wrapper — consecutive text blocks
tagged with a listItem type and a level form a list, and the
renderer groups them.
A mixed fragment — heading, prose, and a code listing (JSON)
A realistic slice of a chapter: a heading, an introductory paragraph, and a code listing with a highlighted line. Note the prefixed, readable block keys.
[
{
"_type": "block",
"_key": "sec-hello-heading",
"style": "h2",
"markDefs": [],
"children": [
{ "_type": "span", "_key": "s1", "text": "A first server", "marks": [] }
]
},
{
"_type": "block",
"_key": "sec-hello-intro",
"style": "normal",
"markDefs": [],
"children": [
{ "_type": "span", "_key": "s1", "text": "The ", "marks": [] },
{ "_type": "span", "_key": "s2", "text": "net/http", "marks": ["code"] },
{ "_type": "span", "_key": "s3", "text": " package ships a production-grade server in Go's standard library:", "marks": [] }
]
},
{
"_type": "codeBlock",
"_key": "sec-hello-listing",
"language": "go",
"code": "package main\n\nimport \"net/http\"\n\nfunc main() {\n\thttp.ListenAndServe(\":8080\", nil)\n}",
"filename": "main.go",
"highlightLines": [6],
"showLineNumbers": true
}
]
An aside with an internal link (JSON)
An aside callout whose body links to another Content Lake Resource
using an internalLink annotation. Internal references use the
cl:// scheme — never a raw web URL — so the link stays valid as
content is versioned and moved.
[
{
"_type": "aside",
"_key": "note-see-chapter-9",
"tone": "note",
"title": "Authentication moved",
"body": [
{
"_type": "block",
"_key": "note-body-1",
"style": "normal",
"markDefs": [
{
"_type": "internalLink",
"_key": "ch9-link",
"reference": "cl://packt/document/res_ml_go_ch9/latest#sec-oauth"
}
],
"children": [
{ "_type": "span", "_key": "s1", "text": "The login flow is now covered in ", "marks": [] },
{ "_type": "span", "_key": "s2", "text": "Chapter 9", "marks": ["ch9-link"] },
{ "_type": "span", "_key": "s3", "text": ".", "marks": [] }
]
}
]
}
]
The reference is a cl:// URI of the form
cl://{tenant}/{type}/{resourceId}/{version}#{blockKey} — here it
points at block sec-oauth in Resource res_ml_go_ch9 at the
working state latest. See the
Glossary for the
cl:// scheme.
Name versus fingerprint: _key and blockHash
Every block carries two identifiers, and keeping them straight is worth a moment because they do opposite jobs.
How the two identifiers differ (name vs fingerprint)
Think of a block as a person. Its _key is its name — a stable,
public handle assigned once and kept for life. Its blockHash is
its fingerprint — derived entirely from what the block currently
contains, and it changes the instant the content changes.
_key (the name) |
blockHash (the fingerprint) |
|
|---|---|---|
| What it is | A stable, human-friendly handle | A content-derived hash |
| Stays the same when | You edit the block's text | Never — it tracks the content |
| Changes when | Never (assigned once, kept for life) | Any word, link, or mark changes |
| Used for | Addressing: links, entity anchors, citations point to (resource, _key) |
Deduplication, "provably unchanged" checks, skip-processing enrichment |
Because the _key is stable, an internal link or an entity
annotation that points at a block keeps working after the block is
edited — the name has not changed, only the words. Because the
blockHash tracks content, the platform can tell at a glance whether
two blocks are identical, and can avoid re-running expensive
enrichment on a block whose fingerprint it has already seen.
A _key is a short, URI-safe handle — the platform generates
twelve-character random keys, though any name matching the grammar in
the conformance specification is accepted. It must be unique within
its document, since links address blocks by it. The blockHash is a
SHA-256 hash written as sha256: followed by hexadecimal; its exact
canonicalisation rules are normative and defined in the conformance
specification.
The examples on this page use readable keys such as intro-heading and
sec-hello-listing for clarity. In production the platform assigns
random keys, but the principle is identical: the name is stable, the
fingerprint follows the content.
The normative reference
This page is a business-facing introduction. It deliberately does not
restate the exact rules an implementation must follow — the _key
grammar, the blockHash canonicalisation algorithm and its test vector,
character-offset counting, the structural validity errors, and the size
limits. Those rules are authoritative and live in the platform's
internal PCF specification, so that two independent implementations
produce byte-identical results.
Authoritative source
The conformance detail — identity, key grammar, offsets, validity, and limits — is normative and maintained in the platform PCF specification, not on this site. Where this page and the specification ever appear to differ, the specification wins. Builders integrating against PCF should treat the conformance document and its JSON Schemas as the contract.
Two facts from that specification are worth carrying away, because they shape how PCF behaves in practice:
- Strict about structure, open about vocabulary. Anything that would corrupt identity or addressing — a missing key, a duplicate key, a link pointing at nothing — is rejected on write. But unknown block types and unknown fields are accepted and preserved, so the format can grow without breaking older content.
- Empty is valid. An empty document, an empty paragraph, and an empty span are all legitimate — drafts start empty and fill in over time. Emptiness is a content state, not an error.
Related pages
- PCF reference — the complete developer reference: every block, span, mark, and custom type with its JSON.
- The Content Lake — the store PCF content lives in, and how it fits the wider platform.
- Resources & versions — how PCF documents are held on a Resource and snapshotted into versions.
- Enrichment & the Knowledge Graph — how the entity overlay annotates PCF blocks by position without touching the content.
- Search — how blocks are chunked and indexed for retrieval.
- Core concepts — where Resources, Works, and the rest of the vocabulary fit together.
- Explorer — Chapter IV — the lake and the content format, visualised.