Skip to content

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:

{
  "_type": "span",
  "_key": "s2",
  "text": "Packt Publishing",
  "marks": ["strong", "packt-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

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": [] }
  ]
}

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://):

cl://{tenant}/{type}/{resourceId}/{version}#{blockKey}
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:

{
  "_type": "codeBlock",
  "_key": "code1",
  "language": "python",
  "code": "print(\"Hello, world!\")"
}

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:

{
  "_type": "formula",
  "_key": "eq1",
  "expression": "E = mc^{2}",
  "displayMode": "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:

  • level ranges 1–6; a listItem block without level is level 1.
  • Consecutive blocks sharing the same listItem type and level belong to one list container.
  • A change of listItem type 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 listItem closes all open containers.
  • level present without listItem is 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:

  1. Applies read defaults (style: "normal", marks: []) so an omitted default and an explicit one hash identically.
  2. Sorts each markDefs array by canonical content and rewrites every span's annotation references from keys to ordinal positions (decorators are left verbatim), then sorts each marks array — so re-keying leaves the hash unchanged.
  3. Strips every _key at every depth.
  4. 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 PCF001PCF011 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:

  1. For "block", render its children spans, applying marks and style.
  2. For a known custom type (image, video, codeBlock, formula, table, aside), render its type-specific structure.
  3. 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.

  • 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 _key and 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 PCF001PCF011 error reasons, and how PCF is read and written.
  • Explorer — Chapter IV — the lake and the content format, visualised.