PCF reference
This is the complete type reference for the Packt Content Format (PCF) — the format the Content Lake stores content in. It is written for integrators who parse, serialise, or render PCF themselves: everything here is the machine-facing detail behind the friendly overview on The content format. If you only want to understand what PCF is and why it exists, start there. This page assumes you have, and gets straight to the shapes.
PCF is a superset of Portable Text: a
document is a bare JSON array of typed blocks, each block a bag of
fields keyed by _type. Every valid Portable Text document is a valid
PCF document once _key values are assigned. PCF adds required stable
keys, a set of publishing-specific block and annotation types, and a
separate entity layer — all additive, none of it conflicting with the
base standard. If you already have a Portable Text renderer, you extend
it; you do not replace it.
PCF has two layers, and this page documents only the first. The
content layer is the document itself — the block array specified
here. The entity overlay is a separate JSON document that annotates
the content with references to the Packt
Knowledge Graph,
addressing blocks by their _key and a character range, without
touching the words. The two evolve independently: content renders
perfectly with no overlay, and the overlay can be regenerated without
rewriting a single block. The overlay's own shape is covered in
Enrichment & the Knowledge Graph.
The golden rule for parsers — skip unknown types, never error
PCF is strict about structure, open about vocabulary. A conformant
parser MUST accept any object that satisfies the structural rules
(_type, _key, key grammar and uniqueness), even if it has never
seen that _type before, and preserve its fields verbatim. A
serialiser that meets a _type it cannot render MUST skip the block
gracefully or emit a placeholder — it MUST NOT fail. This one rule is
what lets PCF grow new block types without breaking every consumer
already in the field. Build for it from day one.
The type index below is your map. Each section gives the full field table and copy-paste-valid JSON. All the JSON on this page validates against the normative specification; paste any fragment into your parser and it should round-trip.
| Category | Types |
|---|---|
| Text | block, span |
| Decorators | strong, em, underline, strike, code, superscript, subscript |
| Annotations | link, internalLink, footnote, endnote, citation, crossRef, comment |
| Custom blocks | image, video, codeBlock, formula, table, aside |
Document structure and the two universal fields
A PCF document is a JSON array. There is no envelope object and no version field inside the document — the format version travels out of band (in the API media-type parameter and on the Resource metadata), so that a PCF document stays a plain Portable Text array. The root of a document is simply its blocks, in order, and block order is significant and preserved exactly.
flowchart TD
DOC["PCF document<br/>(ordered array)"]
DOC --> B1["block · text"]
DOC --> B2["image · video · codeBlock<br/>formula · table · aside"]
DOC --> B3["block · list item"]
B1 --> MD["markDefs<br/>(annotations)"]
B1 --> SP["children<br/>(spans + inline types)"]
SP --> DEC["decorators<br/>(strong, em, code, …)"]
SP --> ANN["annotation refs<br/>→ markDefs"]
Every element in the root array — and every span, and every mark definition — carries exactly two universal fields. These are the two handles the whole platform addresses content by, so getting them right is the first job of any producer.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | The block or element type identifier — "block", "span", "image", or any custom string. The write path records it verbatim, known or not. |
_key |
string | Yes | A stable, document-unique handle for addressing this element. Assigned once, kept for life. |
_key is the element's logical identity — its name. It is assigned
once, stays stable across every edit to the content it names, and is
what internal links, entity-overlay anchors, citations, and
cross-references point at. Keys are [A-Za-z0-9_-]{1,64} (URI-fragment
safe, because they appear after # in cl:// references);
platform-generated keys are twelve-character random base62. The examples
here use readable keys like intro-h1 for clarity — production uses
random ones, but the grammar is identical.
Separately, every root-level block has a blockHash: its content
identity, a deterministic SHA-256 fingerprint that changes exactly when
the block's content changes. It is not written into the document array
— it is computed and stored alongside — but it is central to how the
platform deduplicates storage and skips reprocessing. Keep the two
straight: _key is the name (stable), blockHash is the fingerprint
(follows the content). The exact algorithm is in
Conformance essentials.
Key uniqueness scopes
Block keys are unique across the whole document — including
blocks nested inside body arrays (asides, footnotes, endnotes) and
rich-text captions — so a fragment #{blockKey} is always
unambiguous. Span keys and mark-definition keys are unique
only within their containing block. Only root-level blocks are
addressable by fragment; blocks inside a nested body are part of their
parent's content and cannot be targeted independently.
Text blocks
The block type is the workhorse: a paragraph, a heading, a
blockquote, or a list item. Its words live in its children array as
spans, and any annotations those spans
reference are defined once in the block's markDefs.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "block" |
Always "block" for text blocks. |
_key |
string | Yes | — | Document-unique identifier. |
style |
string | No | "normal" |
Visual role. Parsers apply the default on read; an omitted style is "normal". |
markDefs |
array | No | [] |
Annotation definitions that child spans reference by key. |
children |
array | Yes | — | Array of span objects and inline custom types. Required — a text block with no children array is a structural error (PCF006). May be empty. |
listItem |
string|null | No | null |
List marker type when this block is a list item — see Lists. |
level |
integer|null | No | null |
List nesting depth, 1–6. Present only with listItem; otherwise a structural error (PCF009). |
Style values
| Style | Semantic meaning |
|---|---|
"normal" |
Body paragraph (the default). |
"h1" – "h6" |
Heading levels 1 through 6. |
"blockquote" |
Block quotation. |
Out-of-vocabulary style values are accepted on write and preserved. A
consumer that does not recognise a style MUST render the block as
normal rather than failing.
{
"_type": "block",
"_key": "intro-h1",
"style": "h1",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "Chapter 1: Getting Started",
"marks": []
}
]
}
Spans
A span is the fundamental inline text unit inside a block's
children. A run of text that shares the same formatting is one span;
where formatting changes, a new span begins. Formatting is applied
through the span's marks.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "span" |
Always "span" for text spans. |
_key |
string | Yes | — | Block-unique identifier. |
text |
string | Yes | — | The text content. May be the empty string — an empty span is valid. Normalised to Unicode NFC on write. |
marks |
array | No | [] |
Decorator names and/or markDefs keys applied to this span. Parsers apply the [] default on read. |
Each entry in marks is either a decorator name (a
fixed string like "strong") or the _key of an
annotation in the enclosing block's markDefs. A span
may carry several marks at once — a phrase can be both bold and a link:
Dangling mark references are rejected
Every entry in a span's marks MUST be either a recognised decorator
or a _key present in the enclosing block's markDefs. A marks
entry that names neither is a dangling reference and rejected on write
(PCF005).
Marks
Marks annotate spans, and come in two forms. Decorators are simple
string flags with no extra data — you just add the name to a span's
marks. Annotations carry structured data (a URL, a footnote body,
bibliographic fields), so each is defined once as an object in the
block's markDefs and referenced from a span by its _key.
Decorators
Decorators are the plain-string entries in a marks array. PCF defines
seven standard ones; a producer may emit others, and an unknown
decorator is preserved but rendered as plain text by consumers that do
not recognise it.
| Decorator | Meaning |
|---|---|
"strong" |
Bold / strong emphasis. |
"em" |
Italic / emphasis. |
"underline" |
Underline. |
"strike" |
Strikethrough. |
"code" |
Inline code. |
"superscript" |
Superscript text. |
"subscript" |
Subscript text. |
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [],
"children": [
{ "_type": "span", "_key": "s1", "text": "Water is H", "marks": [] },
{ "_type": "span", "_key": "s2", "text": "2", "marks": ["subscript"] },
{ "_type": "span", "_key": "s3", "text": "O. This is ", "marks": [] },
{
"_type": "span",
"_key": "s4",
"text": "bold and italic",
"marks": ["strong", "em"]
},
{ "_type": "span", "_key": "s5", "text": ".", "marks": [] }
]
}
Annotations
Annotations are objects in a block's markDefs array. Each has its own
_type and _key; a span opts in by listing the annotation's _key
in its marks. PCF defines seven standard annotation types. Six render
in output; comment never does.
_type |
Purpose | Rendered in output? |
|---|---|---|
"link" |
External hyperlink | Yes |
"internalLink" |
Cross-document Packt content link | Yes |
"footnote" |
Page-level note with a rich-text body | Yes |
"endnote" |
Document-level note with a rich-text body | Yes |
"citation" |
Structured bibliographic reference | Yes (in-text + bibliography) |
"crossRef" |
Intra-document block reference | Yes (as link/label) |
"comment" |
Editorial workflow annotation | No |
link
A hyperlink to an external URL.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "link". |
_key |
string | Yes | Block-unique identifier; referenced from a span's marks. |
href |
string | Yes | The target URL. |
title |
string|null | No | Optional tooltip / title text. |
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "link",
"_key": "packt-link",
"href": "https://www.packtpub.com",
"title": "Packt Publishing"
}
],
"children": [
{ "_type": "span", "_key": "s1", "text": "Find more at the ", "marks": [] },
{
"_type": "span",
"_key": "s2",
"text": "Packt website",
"marks": ["packt-link"]
},
{ "_type": "span", "_key": "s3", "text": ".", "marks": [] }
]
}
internalLink
A reference to another piece of Packt content — a different document,
chapter, or block. Unlike link, it does not carry a web URL; it
carries a cl:// URI that stays valid as content is versioned and
moved.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "internalLink". |
_key |
string | Yes | Block-unique identifier. |
reference |
string | Yes | A cl:// URI identifying the target document and, optionally, a block within it. |
The reference uses the cl:// scheme (never a web URL, and never the
retired contentlake://):
| Segment | Description |
|---|---|
tenant |
The tenant identifier (e.g. packt). |
type |
The resource type segment — document for a concrete document Resource. Generic URI templates use {type}. |
resourceId |
The res_-prefixed ID of the target Resource. |
version |
A ver_ version to pin, or latest to track the current working state. |
blockKey |
Optional fragment: the _key of a specific root-level block in the target. Omit to point at the document as a whole. |
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "internalLink",
"_key": "ch3-link",
"reference": "cl://packt/document/res_ml_go_ch3/latest#h2a"
}
],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "As we discussed in ",
"marks": []
},
{
"_type": "span",
"_key": "s2",
"text": "Chapter 3",
"marks": ["ch3-link"]
},
{
"_type": "span",
"_key": "s3",
"text": ", the event-driven architecture provides several advantages.",
"marks": []
}
]
}
footnote
A note rendered at the bottom of the page (print/PDF) or as a
tooltip/popover (digital). The body is a full nested PCF document
array, supporting rich text.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "footnote". |
_key |
string | Yes | Block-unique identifier. |
body |
array | Yes | An array of PCF blocks forming the footnote content. Supports full rich text, including links and inline code. |
Serialisers SHOULD auto-number footnotes sequentially in document order; the span the footnote is attached to becomes the anchor.
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "footnote",
"_key": "fn1",
"body": [
{
"_type": "block",
"_key": "fnb1",
"style": "normal",
"markDefs": [
{
"_type": "link",
"_key": "fnlnk1",
"href": "https://pkg.go.dev/net/http"
}
],
"children": [
{ "_type": "span", "_key": "fns1", "text": "The ", "marks": [] },
{
"_type": "span",
"_key": "fns2",
"text": "net/http",
"marks": ["code"]
},
{
"_type": "span",
"_key": "fns3",
"text": " package is part of Go's standard library. See the ",
"marks": []
},
{
"_type": "span",
"_key": "fns4",
"text": "official documentation",
"marks": ["fnlnk1"]
},
{ "_type": "span", "_key": "fns5", "text": ".", "marks": [] }
]
}
]
}
],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "Go ships a production-grade ",
"marks": []
},
{ "_type": "span", "_key": "s2", "text": "HTTP server", "marks": ["fn1"] },
{ "_type": "span", "_key": "s3", "text": " out of the box.", "marks": [] }
]
}
endnote
Structurally identical to footnote, but semantically distinct:
endnotes are collected at the end of a chapter or document rather than
at each page footer. In digital-first formats with no page concept,
serialisers MAY render endnotes identically to footnotes — but the
distinction matters for print, PDF, and EPUB.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "endnote". |
_key |
string | Yes | Block-unique identifier. |
body |
array | Yes | An array of PCF blocks. Same structure as footnote.body. |
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "endnote",
"_key": "en1",
"body": [
{
"_type": "block",
"_key": "enb1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "ens1",
"text": "See Ongaro and Ousterhout, 'In Search of an Understandable Consensus Algorithm' (USENIX ATC 2014).",
"marks": []
}
]
}
]
}
],
"children": [
{ "_type": "span", "_key": "s1", "text": "The ", "marks": [] },
{
"_type": "span",
"_key": "s2",
"text": "Raft consensus algorithm",
"marks": ["strong", "en1"]
},
{
"_type": "span",
"_key": "s3",
"text": " was designed to be understandable.",
"marks": []
}
]
}
citation
A structured bibliographic reference. The span text is the in-text citation (e.g. "Kleppmann, 2017"); the annotation carries the full bibliographic data for a generated bibliography.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "citation". |
_key |
string | Yes | Block-unique identifier. |
authors |
array | Yes | Author names as strings, in "Last, First" format. |
title |
string | Yes | Title of the cited work. |
year |
integer|null | No | Publication year. |
publisher |
string|null | No | Publisher or organisation. |
venue |
string|null | No | Journal, conference, or book where the work appeared. |
doi |
string|null | No | Digital Object Identifier. |
url |
string|null | No | Direct URL to the cited work. |
isbn |
string|null | No | ISBN for book references. |
pages |
string|null | No | Page range (e.g. "305-320"). |
citationKey |
string|null | No | Stable key for bibliography generation (e.g. a BibTeX key). |
Serialisers SHOULD collect all citations in document order and render a
references section; citationKey enables consistent ordering and
deduplication when the same work is cited more than once.
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "citation",
"_key": "cite1",
"authors": ["Kleppmann, Martin"],
"title": "Designing Data-Intensive Applications",
"year": 2017,
"publisher": "O'Reilly Media",
"isbn": "978-1-4493-7332-0",
"citationKey": "kleppmann2017ddia"
}
],
"children": [
{ "_type": "span", "_key": "s1", "text": "As ", "marks": [] },
{
"_type": "span",
"_key": "s2",
"text": "Kleppmann (2017)",
"marks": ["cite1"]
},
{
"_type": "span",
"_key": "s3",
"text": " demonstrates, the consistency-availability trade-off is fundamental.",
"marks": []
}
]
}
crossRef
A reference to another block within the same PCF document, by the
target block's _key. Used for phrases like "see Figure 3.1" or "as
shown in Equation 2.1". The serialiser resolves it to a hyperlink, page
number, or label per medium.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "crossRef". |
_key |
string | Yes | Block-unique identifier. |
targetKey |
string | Yes | The _key of a root-level block in the same document. |
targetType |
string|null | No | The target's _type (e.g. "formula", "image", "table") — a hint for generating prefixes like "Figure" or "Listing". |
label |
string|null | No | Human-readable fallback label (e.g. "Equation 4.1"). Serialisers MAY override it with an auto-generated label. |
A targetKey that names no root-level block is rejected on write
(PCF007). If a target is deleted after the fact, serialisers SHOULD
render the span text as-is without a link.
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "crossRef",
"_key": "xref1",
"targetKey": "eq003",
"targetType": "formula",
"label": "Equation 4.1"
}
],
"children": [
{ "_type": "span", "_key": "s1", "text": "As shown in ", "marks": [] },
{
"_type": "span",
"_key": "s2",
"text": "Equation 4.1",
"marks": ["xref1"]
},
{
"_type": "span",
"_key": "s3",
"text": ", the F1 score balances precision and recall.",
"marks": []
}
]
}
comment
An editorial annotation for the authoring and review workflow. It is never rendered in published output — a serialiser targeting published output MUST drop it.
| Field | Type | Required | Description |
|---|---|---|---|
_type |
string | Yes | Always "comment". |
_key |
string | Yes | Block-unique identifier. |
text |
string | Yes | The comment text. |
author |
string | Yes | Identifier for the comment author. |
createdAt |
string|null | No | ISO 8601 timestamp. |
resolved |
boolean | No | Whether the comment has been addressed. Defaults to false. |
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [
{
"_type": "comment",
"_key": "cmt1",
"text": "This claim needs a citation. Can we reference the 2024 developer survey?",
"author": "editor-jane-smith",
"createdAt": "2026-03-15T14:30:00Z",
"resolved": false
}
],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "TypeScript has overtaken JavaScript for new projects",
"marks": ["cmt1"]
},
{
"_type": "span",
"_key": "s2",
"text": ", according to recent surveys.",
"marks": []
}
]
}
Custom block types
Beyond text, PCF defines six standard custom block types that sit in
the root array alongside block, each with the same _type + _key
contract. Serialisers MUST handle these six; where one cannot be
rendered in a given medium (a text-to-speech engine meeting a
codeBlock, say) it SHOULD produce a meaningful fallback rather than
failing. Custom types beyond these are permitted and encouraged — the
golden rule applies: preserve them, skip what you
cannot render.
Several of these types nest caption or body arrays that are themselves full PCF documents; serialise them with the same pipeline as the top-level array.
image
A static image — photograph, diagram, illustration, or screenshot.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "image" |
Always "image". |
_key |
string | Yes | — | Document-unique identifier. |
asset |
object | Yes | — | Reference to the image asset. At least one of _ref or url MUST be present. |
asset._ref |
string | Conditional | — | Asset identifier within the Packt asset system. Required unless url is given. Serialisers should prefer _ref and resolve it via the asset pipeline. |
asset.url |
string | Conditional | — | Resolved URL for the asset. Required unless _ref is given. |
asset.sizes |
array|null | No | null |
Responsive variants: { "variant": "2x", "url": … } entries at other resolutions. Serialisers pick the variant fitting their medium. |
alt |
string | Yes | — | Alternative text. MUST be non-empty for content images; decorative images MAY use an empty string. |
extendedAlt |
array|null | No | null |
PCF text blocks giving a full rich-text long description for complex images (audio descriptions, long alt). Supplements alt; never duplicates the caption. |
caption |
array|null | No | null |
PCF text blocks for the caption — a nested PCF document supporting full rich text. |
attribution |
string|null | No | null |
Plain-text attribution or licence information. |
width |
integer|null | No | null |
Intrinsic width in pixels — for aspect-ratio calculation, not display sizing. |
height |
integer|null | No | null |
Intrinsic height in pixels. |
layout |
string | No | "default" |
Layout hint (see below). Hints are advisory — a serialiser may ignore an unsupported layout. |
Layout values: "default" (content-width), "wide" (breaks out of
the text column), "full" (full-bleed edge-to-edge), "inline" (small,
inline with text), "float-left", "float-right" (floated with text
wrapping).
Minimal:
{
"_type": "image",
"_key": "img1",
"asset": { "_ref": "asset-arch-overview" },
"alt": "Architecture overview diagram"
}
Full, with a rich caption and layout:
{
"_type": "image",
"_key": "img2",
"asset": {
"_ref": "asset-diagram-k8s",
"url": "https://cdn.packt.com/assets/diagram-k8s/pod-networking.svg"
},
"alt": "Kubernetes pod networking diagram showing east-west traffic flow between services",
"caption": [
{
"_type": "block",
"_key": "cap1",
"style": "normal",
"markDefs": [
{
"_type": "link",
"_key": "lnk1",
"href": "https://kubernetes.io/docs/concepts/cluster-administration/networking/"
}
],
"children": [
{
"_type": "span",
"_key": "cs1",
"text": "Figure 3.2: Pod-to-pod networking. See the ",
"marks": []
},
{
"_type": "span",
"_key": "cs2",
"text": "Kubernetes networking docs",
"marks": ["lnk1"]
},
{
"_type": "span",
"_key": "cs3",
"text": " for more detail.",
"marks": []
}
]
}
],
"attribution": "Adapted from the Kubernetes documentation, Apache 2.0 License",
"width": 1600,
"height": 900,
"layout": "wide"
}
video
An embedded video — third-party hosted (YouTube, Vimeo) or self-hosted in the Packt asset system.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "video" |
Always "video". |
_key |
string | Yes | — | Document-unique identifier. |
url |
string | Yes* | — | The canonical URL of the video. Required if asset is absent. |
provider |
string|null | No | null |
Platform: "youtube", "vimeo", "packt", "wistia", or null for self-hosted / unknown. |
providerVideoId |
string|null | No | null |
The video ID on the provider's platform. |
asset |
object|null | No | null |
Packt asset reference for self-hosted video. Same structure as image.asset. |
title |
string | Yes | — | Human-readable title. Used for accessibility and as fallback display text. |
caption |
array|null | No | null |
PCF text blocks for the caption (same as image.caption). |
thumbnail |
object|null | No | null |
Asset reference or URL for the poster/thumbnail. |
duration |
integer|null | No | null |
Duration in seconds. |
startTime |
integer | No | 0 |
Playback start time in seconds. |
endTime |
integer|null | No | null |
Playback end time in seconds. null plays to the end. |
autoplay |
boolean | No | false |
Whether to autoplay (subject to browser policy). |
loop |
boolean | No | false |
Whether to loop. |
*Either url or asset MUST be provided.
Provider values: "youtube" (providerVideoId is the YouTube ID),
"vimeo" (Vimeo ID), "packt" (Packt-hosted — use asset, not
url), "wistia" (Wistia hash ID), null (generic — use url
directly).
Minimal — YouTube:
{
"_type": "video",
"_key": "vid1",
"url": "https://www.youtube.com/watch?v=rfscVS0vtbw",
"provider": "youtube",
"providerVideoId": "rfscVS0vtbw",
"title": "Learn Python — Full Course for Beginners",
"duration": 15788
}
Full — Packt-hosted with a caption:
{
"_type": "video",
"_key": "vid2",
"asset": { "_ref": "asset-video-grpc-intro" },
"provider": "packt",
"title": "Introduction to gRPC in Go",
"thumbnail": { "_ref": "asset-thumb-grpc-intro" },
"duration": 612,
"startTime": 0,
"endTime": null,
"autoplay": false,
"loop": false,
"caption": [
{
"_type": "block",
"_key": "vcap1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "vcs1",
"text": "Video 1.1: Setting up your first gRPC service.",
"marks": []
}
]
}
]
}
code block
The codeBlock type is a source-code listing — the bread and butter of
technical publishing. It carries language metadata for syntax
highlighting, line numbering, line highlighting, an optional filename,
and an optional caption.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "codeBlock" |
Always "codeBlock". |
_key |
string | Yes | — | Document-unique identifier. |
language |
string | Yes | — | Language identifier, lowercase (e.g. "javascript", "python", "go", "bash", "json"). Unknown identifiers SHOULD render as plaintext, not error. |
code |
string | Yes | — | The source content. Newlines are \n. Stored verbatim — not NFC-normalised, since normalisation can alter source. |
filename |
string|null | No | null |
Optional filename shown above the block (e.g. "server.go"). |
caption |
array|null | No | null |
PCF text blocks for a caption below the block. |
highlightLines |
array|null | No | null |
1-based line numbers to visually highlight. Unknown entries are ignored by consumers. |
showLineNumbers |
boolean | No | true |
Whether to display line numbers. |
startLineNumber |
integer | No | 1 |
First line number to display (for excerpts from larger files). |
diff |
boolean | No | false |
Render as a diff — lines starting +/- highlighted accordingly. |
The following language identifiers MUST be supported by conforming
serialisers: plaintext, javascript, typescript, python, go,
rust, java, csharp, cpp, c, ruby, php, swift, kotlin,
bash, shell, powershell, sql, html, css, scss, json,
yaml, toml, xml, markdown, dockerfile, hcl, graphql,
protobuf, solidity. The set is not fixed — any other identifier is
accepted.
Minimal:
Full — with a filename and highlighted lines:
{
"_type": "codeBlock",
"_key": "code2",
"language": "go",
"code": "package main\n\nimport \"fmt\"\n\nfunc fibonacci(n int) int {\n\tif n <= 1 {\n\t\treturn n\n\t}\n\treturn fibonacci(n-1) + fibonacci(n-2)\n}",
"filename": "fibonacci.go",
"caption": [
{
"_type": "block",
"_key": "ccap1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "ccs1",
"text": "Listing 2.1: A naive recursive Fibonacci in Go.",
"marks": []
}
]
}
],
"highlightLines": [5, 6, 7, 8, 9],
"showLineNumbers": true,
"startLineNumber": 1,
"diff": false
}
Diff view:
{
"_type": "codeBlock",
"_key": "code3",
"language": "javascript",
"code": " function processOrder(order) {\n- const total = order.items.reduce((sum, i) => sum + i.price, 0);\n+ const total = order.items.reduce((sum, i) => sum + (i.price * i.quantity), 0);\n return { ...order, total };\n }",
"filename": "orderService.js",
"showLineNumbers": true,
"diff": true
}
formula
The formula block is a mathematical expression, stored as a
LaTeX/KaTeX string. It appears either as a standalone display block in
the root array (displayMode: "block") or inline inside a text
block's children alongside spans (displayMode: "inline").
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "formula" |
Always "formula". |
_key |
string | Yes | — | Document-unique identifier. |
expression |
string | Yes | — | The expression as a LaTeX/KaTeX string. Standard math-mode syntax — do not include surrounding $ or $$ delimiters. |
format |
string | No | "latex" |
Notation format. Only "latex" is currently specified; reserved for future formats. |
displayMode |
string | Yes | — | "block" for a centred display equation, "inline" for an inline expression. |
label |
string|null | No | null |
An equation label/number (e.g. "3.2"). Serialisers SHOULD render it in parentheses to the right of the equation. |
caption |
array|null | No | null |
PCF text blocks for an optional caption below the formula. |
alt |
string|null | No | null |
Plain-text description for accessibility. Strongly recommended for complex expressions. |
Minimal — display block:
Full — labelled with a caption and accessibility text:
{
"_type": "formula",
"_key": "eq2",
"expression": "\\nabla \\times \\mathbf{E} = -\\frac{\\partial \\mathbf{B}}{\\partial t}",
"format": "latex",
"displayMode": "block",
"label": "2.1",
"caption": [
{
"_type": "block",
"_key": "fcap1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "fcs1",
"text": "Faraday's law of electromagnetic induction.",
"marks": []
}
]
}
],
"alt": "The curl of the electric field equals the negative partial derivative of the magnetic field with respect to time"
}
Inline — a formula sits inside a block's children array:
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "The time complexity of merge sort is ",
"marks": []
},
{
"_type": "formula",
"_key": "eq3",
"expression": "O(n \\log n)",
"displayMode": "inline"
},
{
"_type": "span",
"_key": "s2",
"text": ", faster than bubble sort's ",
"marks": []
},
{
"_type": "formula",
"_key": "eq4",
"expression": "O(n^{2})",
"displayMode": "inline"
},
{ "_type": "span", "_key": "s3", "text": " for large inputs.", "marks": [] }
]
}
table
The table block is structured tabular data — comparison tables, data
grids, API references. Cells hold full rich text (bold, code, links).
Its two-dimensional structure and spanning support are why it is a
first-class type rather than a sequence of text blocks.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "table" |
Always "table". |
_key |
string | Yes | — | Document-unique identifier. |
caption |
array|null | No | null |
PCF text blocks for a caption (above or below per serialiser convention). |
markDefs |
array | No | [] |
Annotation definitions for cell spans. Same shape as a text block's markDefs; annotations in any cell resolve against the table's own markDefs. |
headerRow |
boolean | No | false |
Whether the first row is a header row. A convenience flag — serialisers SHOULD also honour per-cell header. |
headerColumn |
boolean | No | false |
Whether the first column is a header column. |
rows |
array | Yes | — | Array of row objects. |
Row object: _key (string, required, row-unique) and cells
(array, required, cell objects in column order).
Cell object:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_key |
string | Yes | — | Cell-unique identifier. |
content |
array | Yes | — | Array of PCF spans (same as a block's children). Supports all marks; annotations resolve against the table's markDefs. |
header |
boolean | No | false |
Whether this is a header cell (rendered as <th>). |
rowSpan |
integer | No | 1 |
Rows this cell spans. |
colSpan |
integer | No | 1 |
Columns this cell spans. |
Spanning rules: when a cell has rowSpan/colSpan greater than 1,
the positions it covers in subsequent rows/columns MUST be omitted from
their cells arrays — do not include placeholder cells; serialisers
derive the grid from the span values. A cell MAY span both directions
at once. Consumers SHOULD validate that spans neither overflow the grid
nor overlap, and treat an invalid span as 1.
Minimal — a two-column table with a header row:
{
"_type": "table",
"_key": "tbl1",
"headerRow": true,
"rows": [
{
"_key": "r1",
"cells": [
{
"_key": "c1",
"content": [
{
"_type": "span",
"_key": "s1",
"text": "Command",
"marks": ["strong"]
}
],
"header": true
},
{
"_key": "c2",
"content": [
{
"_type": "span",
"_key": "s2",
"text": "Description",
"marks": ["strong"]
}
],
"header": true
}
]
},
{
"_key": "r2",
"cells": [
{
"_key": "c3",
"content": [
{
"_type": "span",
"_key": "s3",
"text": "go build",
"marks": ["code"]
}
]
},
{
"_key": "c4",
"content": [
{
"_type": "span",
"_key": "s4",
"text": "Compile packages and dependencies",
"marks": []
}
]
}
]
}
]
}
Rich cells with a table-level link and a caption:
{
"_type": "table",
"_key": "tbl2",
"headerRow": true,
"caption": [
{
"_type": "block",
"_key": "tcap1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "tcs1",
"text": "Table 3.1: HTTP methods and idempotency.",
"marks": []
}
]
}
],
"markDefs": [
{
"_type": "link",
"_key": "lnk1",
"href": "https://www.rfc-editor.org/rfc/rfc9110"
}
],
"rows": [
{
"_key": "r1",
"cells": [
{
"_key": "c1",
"content": [
{
"_type": "span",
"_key": "s1",
"text": "Method",
"marks": ["strong"]
}
],
"header": true
},
{
"_key": "c2",
"content": [
{
"_type": "span",
"_key": "s2",
"text": "Idempotent",
"marks": ["strong"]
}
],
"header": true
},
{
"_key": "c3",
"content": [
{
"_type": "span",
"_key": "s3",
"text": "Notes",
"marks": ["strong"]
}
],
"header": true
}
]
},
{
"_key": "r2",
"cells": [
{
"_key": "c4",
"content": [
{ "_type": "span", "_key": "s4", "text": "GET", "marks": ["code"] }
]
},
{
"_key": "c5",
"content": [
{
"_type": "span",
"_key": "s5",
"text": "Yes",
"marks": ["strong"]
}
]
},
{
"_key": "c6",
"content": [
{
"_type": "span",
"_key": "s6",
"text": "Retrieve a resource. Defined in ",
"marks": []
},
{
"_type": "span",
"_key": "s6a",
"text": "RFC 9110",
"marks": ["lnk1"]
},
{ "_type": "span", "_key": "s6b", "text": ".", "marks": [] }
]
}
]
}
]
}
Column spanning — a merged header cell over two columns (note the covered position in the next row is omitted):
{
"_type": "table",
"_key": "tbl3",
"headerRow": true,
"rows": [
{
"_key": "r1",
"cells": [
{
"_key": "c1",
"content": [
{
"_type": "span",
"_key": "s1",
"text": "Service",
"marks": ["strong"]
}
],
"header": true
},
{
"_key": "c2",
"content": [
{
"_type": "span",
"_key": "s2",
"text": "Resource Limits",
"marks": ["strong"]
}
],
"header": true,
"colSpan": 2
}
]
},
{
"_key": "r2",
"cells": [
{
"_key": "c3",
"content": [
{ "_type": "span", "_key": "s3", "text": "", "marks": [] }
]
},
{
"_key": "c4",
"content": [
{
"_type": "span",
"_key": "s4",
"text": "CPU",
"marks": ["strong"]
}
],
"header": true
},
{
"_key": "c5",
"content": [
{
"_type": "span",
"_key": "s5",
"text": "Memory",
"marks": ["strong"]
}
],
"header": true
}
]
},
{
"_key": "r3",
"cells": [
{
"_key": "c6",
"content": [
{
"_type": "span",
"_key": "s6",
"text": "API Gateway",
"marks": []
}
]
},
{
"_key": "c7",
"content": [
{ "_type": "span", "_key": "s7", "text": "250m", "marks": ["code"] }
]
},
{
"_key": "c8",
"content": [
{
"_type": "span",
"_key": "s8",
"text": "128Mi",
"marks": ["code"]
}
]
}
]
}
]
}
aside
The aside block is a callout, admonition, or sidebar — a tip,
warning, or note that supplements the main narrative. It is a
first-class type (rather than a style on a text block) because it
holds a rich inner body — potentially multiple paragraphs, code, or
images — carries a semantic tone, and is optionally renderable:
formats that cannot represent asides MAY omit them entirely.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
_type |
string | Yes | "aside" |
Always "aside". |
_key |
string | Yes | — | Document-unique identifier. |
tone |
string | Yes | — | Semantic tone, driving visual treatment (see below). |
title |
string|null | No | null |
Optional heading. If omitted, serialisers MAY use the tone as a default title (e.g. "Warning"). |
body |
array | Yes | — | An array of PCF blocks — a nested PCF document supporting full rich text, code blocks, and images. |
Tone values — serialisers MUST support all eight; an unknown custom
tone SHOULD fall back to "info" styling: "info" (general
supplementary info), "tip" (a helpful suggestion), "warning" (a
cautionary note), "danger" (a critical "never do this" warning),
"idea" (an inspirational prompt), "note" (neutral aside),
"deprecated" (outdated approach/API), "experimental" (unstable /
preview feature).
Minimal — a tip:
{
"_type": "aside",
"_key": "aside1",
"tone": "tip",
"body": [
{
"_type": "block",
"_key": "ab1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "as1",
"text": "Use tab-completion to avoid typos with long kubectl resource names.",
"marks": []
}
]
}
]
}
Full — a warning whose body nests a code block:
{
"_type": "aside",
"_key": "aside2",
"tone": "warning",
"title": "Breaking Change in v3.0",
"body": [
{
"_type": "block",
"_key": "ab1",
"style": "normal",
"markDefs": [],
"children": [
{ "_type": "span", "_key": "as1", "text": "The ", "marks": [] },
{
"_type": "span",
"_key": "as2",
"text": "createClient()",
"marks": ["code"]
},
{
"_type": "span",
"_key": "as3",
"text": " function no longer accepts a plain string URL. Pass an options object:",
"marks": []
}
]
},
{
"_type": "codeBlock",
"_key": "acode1",
"language": "typescript",
"code": "// After (v3.x)\nconst client = createClient({\n baseUrl: 'https://api.example.com',\n timeout: 5000,\n});",
"diff": false,
"showLineNumbers": true
}
]
}
Inline custom types
Custom types can also appear inside a block's children array as
inline elements, sitting between spans. Like all elements they carry a
_type and _key, but — unlike spans — they have no text field.
Inline formula (displayMode: "inline") is the standard example; a
producer may define others. Consumers apply the same golden rule: an
unknown inline type is preserved on write and skipped by renderers that
cannot handle it.
{
"_type": "block",
"_key": "b1",
"style": "normal",
"markDefs": [],
"children": [
{
"_type": "span",
"_key": "s1",
"text": "As of market close, ",
"marks": []
},
{
"_type": "stockTicker",
"_key": "st1",
"symbol": "MSFT",
"exchange": "NASDAQ"
},
{
"_type": "span",
"_key": "s2",
"text": " was up 2.3% on the day.",
"marks": []
}
]
}
Lists
PCF has no list wrapper element. A list is a run of consecutive
blocks each carrying a listItem marker and a level; the serialiser
derives the list structure from the sequence. This keeps the document a
flat, reorderable array while still expressing arbitrary nesting.
List-item markers (listItem): "bullet" (unordered disc),
"number" (ordered numeric), "square" (unordered square), "letter"
(ordered alphabetic).
Grouping rules a serialiser follows:
levelranges 1–6; alistItemblock withoutlevelis level 1.- Consecutive blocks sharing the same
listItemtype andlevelbelong to one list container. - A change of
listItemtype at the same level closes the current container and opens a new one. - A jump of more than one level (1 → 3) materialises the intermediate containers — the HTML nesting model.
- Any block without
listItemcloses all open containers. levelpresent withoutlistItemis a structural error (PCF009).
[
{
"_type": "block",
"_key": "li1",
"style": "normal",
"markDefs": [],
"listItem": "bullet",
"level": 1,
"children": [
{ "_type": "span", "_key": "s1", "text": "Frontend", "marks": ["strong"] }
]
},
{
"_type": "block",
"_key": "li2",
"style": "normal",
"markDefs": [],
"listItem": "bullet",
"level": 2,
"children": [
{ "_type": "span", "_key": "s2", "text": "React 18", "marks": [] }
]
},
{
"_type": "block",
"_key": "li3",
"style": "normal",
"markDefs": [],
"listItem": "bullet",
"level": 1,
"children": [
{ "_type": "span", "_key": "s3", "text": "Backend", "marks": ["strong"] }
]
}
]
Conformance essentials for implementers
The parts of PCF that must be bit-identical across implementations — block identity, key grammar, offsets, validity, and limits — are normative and live in the platform's internal PCF conformance specification. What follows is the working summary; where this page and that specification ever differ, the specification wins.
Authoritative source
The conformance detail — the blockHash canonicalisation algorithm
and its test vector, the _key grammar, character-offset counting,
the structural validity errors, and the size limits — is maintained in
the platform PCF specification, not on this site. Builders integrating
against PCF should treat the conformance document and its JSON Schemas
as the contract.
The blockHash fingerprint
blockHash is a root-level block's content identity — a SHA-256 hash
that changes exactly when the block's content changes. It drives storage
deduplication across versions and lets the platform skip re-running
entity extraction and embeddings on content it has already processed. It
is stored as sha256: followed by 64 lowercase hex characters.
Because re-ingesting an unchanged source regenerates every _key, keys
MUST NOT participate in the hash. Conceptually the algorithm:
- Applies read defaults (
style: "normal",marks: []) so an omitted default and an explicit one hash identically. - Sorts each
markDefsarray by canonical content and rewrites every span's annotation references from keys to ordinal positions (decorators are left verbatim), then sorts eachmarksarray — so re-keying leaves the hash unchanged. - Strips every
_keyat every depth. - Serialises to RFC 8785 canonical JSON and hashes.
Hashes are computed for root-level blocks only (blocks in nested bodies are hashed as part of their parent) and are scoped to a single Resource. RFC 8785 canonical form is used solely for hashing — stored and served documents are ordinary JSON that preserve the producer's array order.
Validity — strict structure, open vocabulary
The write path rejects anything that corrupts identity or addressing, and accepts everything else. The structural errors:
| Code | Rule violated |
|---|---|
PCF001 |
A block, span, or mark definition is missing _type or _key. |
PCF002 |
A _key violates the grammar [A-Za-z0-9_-]{1,64}. |
PCF003 |
Duplicate block _key in the document. |
PCF004 |
Duplicate span or markDef _key within a block. |
PCF005 |
A span marks entry names neither a standard decorator nor a _key in the enclosing block's markDefs. |
PCF006 |
A text block has no children array. |
PCF007 |
A crossRef.targetKey names no root-level block. |
PCF008 |
A footnote/endnote body contains a footnote, endnote, or aside; or an aside body contains an aside, footnote, or endnote. |
PCF009 |
level present on a block without listItem. |
PCF010 |
A size or count limit (below) is exceeded. |
PCF011 |
The document is not well-formed JSON, or its root is not an array. |
These surface on the Public API as reasons PCF001–PCF011 in the
contentlake.packt.com domain (see the
Public API errors). By contrast, unknown block
types, unknown inline types, unknown fields on known types, and
out-of-vocabulary style values are all accepted and preserved —
round-tripping a document MUST NOT drop or reorder the fields a producer
wrote. An empty document ([]), an empty children array, and a span
with text: "" are all valid; emptiness is a content state, not an
error.
Text offsets
All PCF text is Unicode, serialised as UTF-8 JSON. Span text is
normalised to NFC on write (only span text — codeBlock.code and
other literal fields are stored verbatim). Every character offset in the
ecosystem — entity-overlay mention ranges especially — counts Unicode
code points, zero-based, half-open. In UTF-16 languages (JavaScript,
Java) you MUST convert: [...text].slice(start, end).join("") extracts
a range correctly; text.substring(start, end) does not once the text
contains characters outside the Basic Multilingual Plane.
Limits
| Limit | Value |
|---|---|
| Serialised size of one block (UTF-8 JSON, uncompressed) | 256 KB |
| Blocks per document (root level) | 10,000 |
| Serialised size of one document | 25 MB |
| Table rows | 1,000 |
| Table columns | 64 |
| Spans per block | 2,000 |
| Mark definitions per block | 500 |
Nesting depth is at most one: notes and asides MUST NOT contain notes or
asides. Producers with oversized content split it — a table beyond the
row cap becomes consecutive table blocks; prose beyond the size cap is
split at paragraph boundaries. Exceeding any cap is PCF010.
Serialisation and the skip-unknown rule
PCF is exchanged as JSON (UTF-8) only, and the media type is
application/vnd.packt.pcf+json; version=1. Serialisation happens in
consuming layers — the Public API edge (which renders Markdown via
content negotiation), rendering pipelines, product applications — never
in the Content Lake, which stores and serves only PCF JSON, the
highest-fidelity form. A serialiser walks the block array and dispatches
on _type:
- For
"block", render its children spans, applying marks and style. - For a known custom type (
image,video,codeBlock,formula,table,aside), render its type-specific structure. - For an unknown type, skip gracefully or render a placeholder — never fail.
Two stateful concerns aside — list grouping (no wrapper
element) and note auto-numbering — each block is self-contained, so
partial and parallel rendering are straightforward. Libraries are
expected to take a map of type-to-renderer functions, following
@portabletext/react and similar tools.
Related pages
- The content format — the friendly, business-level overview this page is the exhaustive counterpart to.
- Enrichment & the Knowledge Graph — the entity overlay,
the second PCF layer, which annotates these blocks by
_keyand range. - Resources & versions — how a PCF document is held on a Resource and snapshotted into versions.
- Search — how blocks are chunked and indexed for retrieval.
- The Public API — content negotiation, the
PCF001–PCF011error reasons, and how PCF is read and written. - Explorer — Chapter IV — the lake and the content format, visualised.