Skip to content

The content format

When a manuscript arrives, the temptation is to keep it as a Word file, or an HTML page, or a Markdown document — a single blob shaped for one destination. The moment you do that, the content is stuck: to make an ebook you re-flow it, to make an audiobook you re-key it, to feed it to an AI assistant you scrape it back apart. Every format becomes a fresh copy, and the copies drift.

The Content Lake avoids that trap by storing content not as a blob but as structured blocks. A chapter is an ordered list of discrete pieces — a heading, a paragraph, a code listing, an image, a table — each held as data rather than as marked-up text. Because the meaning is captured (this is a heading; that is a link) rather than its appearance in one medium, a single stored source can be rendered faithfully to any target. One paragraph becomes a line of Markdown, a snippet of HTML, or a sentence a narrator reads aloud — all from the same underlying record. This is the format the Content Lake speaks: Packt Content Format (PCF).

Storing content this way pays off far beyond rendering. Because each block is addressable, edits can be diffed cleanly, search can chunk the content into meaningful pieces, and the Knowledge Graph can annotate a phrase by position without ever touching the words. And because PCF describes structure and semantics — never presentation — the platform holds content once and lets every downstream product, channel, and AI surface take the shape it needs.

This page explains the shape of PCF for a general reader: what blocks, spans, and marks are, how the same content renders four different ways, and how a block's stable name differs from its content fingerprint. The JSON here is illustrative. The exact, bit-for-bit rules that two independent implementations must agree on live in the platform PCF conformance specification, which is the authoritative source.

One source, rendered anywhere

The whole point of PCF is that the same stored content can be handed to different renderers and each produces the right thing for its medium. A renderer walks the blocks in order and decides what a heading or a link means where it is going: bold on a web page, a page number in print, a spoken cue in an audiobook.

flowchart LR
    PCF["PCF<br/>(stored once<br/>as structured blocks)"]
    PCF --> MD["Markdown"]
    PCF --> HTML["HTML / web"]
    PCF --> EPUB["ePub · PDF · print"]
    PCF --> AUDIO["Audiobook<br/>narration"]
    PCF --> AI["AI retrieval<br/>& search"]

The tabs below show one small fragment — a heading followed by a paragraph whose middle phrase is a link — in its stored PCF form and in three rendered outputs. Nothing about the content changes between tabs; only the renderer differs.

[
  {
    "_type": "block",
    "_key": "intro-heading",
    "style": "h1",
    "markDefs": [],
    "children": [
      {
        "_type": "span",
        "_key": "s1",
        "text": "Introduction",
        "marks": []
      }
    ]
  },
  {
    "_type": "block",
    "_key": "intro-para",
    "style": "normal",
    "markDefs": [
      {
        "_type": "link",
        "_key": "packt-link",
        "href": "https://www.packtpub.com"
      }
    ],
    "children": [
      {
        "_type": "span",
        "_key": "s1",
        "text": "Introducing the ",
        "marks": []
      },
      {
        "_type": "span",
        "_key": "s2",
        "text": "Packt",
        "marks": ["packt-link"]
      },
      {
        "_type": "span",
        "_key": "s3",
        "text": " content format to enhance publishing flows and integrate context to content.",
        "marks": []
      }
    ]
  }
]
# Introduction

Introducing the [Packt](https://www.packtpub.com) content format to
enhance publishing flows and integrate context to content.
<h1>Introduction</h1>

<p>Introducing the <a href="https://www.packtpub.com">Packt</a>
content format to enhance publishing flows and integrate context to
content.</p>
<speak>
  <p><s>Chapter one. Introduction.</s></p>
  <p><s>Introducing the Packt content format to enhance publishing
  flows and integrate context to content.</s></p>
</speak>

Notice how the link survives as a real hyperlink in HTML and Markdown but is flattened to plain narration in the audiobook — the renderer decides what a link means in its medium. The heading becomes a spoken "Chapter one" cue rather than large text. This faithfulness across every medium is exactly what the structure-not-presentation design buys.

Where rendering happens

The Content Lake stores and serves PCF JSON only — the highest-fidelity form. Rendered formats such as Markdown are produced at the Public API edge through content negotiation: a caller asks for text/markdown and the edge serialises PCF into it on the way out. Markdown, HTML, ePub, and narration are outputs, never something the Content Lake holds.

Blocks, spans, and marks

A PCF document is, at its root, an ordered array of blocks. Most blocks are text — a paragraph, a heading, a list item, a blockquote — but images, code listings, tables, formulae, and callouts are blocks too, sitting at the same level. Reordering, filtering, or transforming content is just array work.

Inside a text block, the words live in spans. A span is a run of text that shares the same formatting. When part of a sentence is bold or is a link, that part becomes its own span; the plain text around it stays in separate spans. Formatting is applied to a span through marks.

Marks come in two kinds. A decorator is a simple label like strong (bold) or em (italic) — it needs no extra data, so it is just a string in the span's marks list. An annotation carries structured data — a link needs a URL, a footnote needs a body — so it is defined once in the block's markDefs list under a key, and a span opts in by naming that key in its marks.

flowchart TD
    DOC["PCF document<br/>(ordered array of blocks)"]
    DOC --> B1["block · heading"]
    DOC --> B2["block · paragraph"]
    DOC --> B3["image · code · table …"]
    B2 --> MD["markDefs<br/>(link, footnote, …)"]
    B2 --> SP1["span · 'Introducing the '"]
    B2 --> SP2["span · 'Packt'"]
    B2 --> SP3["span · ' content format…'"]
    SP2 -->|"marks: strong + link"| MK["decorator 'strong'<br/>+ annotation → markDef"]
    MK -.-> MD

The one-paragraph example in the tabs above shows all three at work: two blocks (a heading and a paragraph), the paragraph split into three spans, and a link annotation defined in markDefs that the middle span references by its key. A single span can carry several marks at once — a phrase can be both bold and a link by listing both.

It is a superset of Portable Text

PCF builds on the open Portable Text standard, so the block / span / markDefs model is familiar to a wide tooling ecosystem. PCF adds required stable keys, publishing-specific block types, and a separate entity layer — all additive. Any valid Portable Text document becomes valid PCF once keys are assigned.

The building blocks you will meet

Beyond plain text, PCF defines a small set of standard block types that cover almost everything in a technical book. Each is stored as data with its own fields, so a renderer can treat it appropriately — and a renderer that cannot handle one (a text-to-speech engine meeting a code listing, say) produces a sensible fallback rather than failing.

Block type What it holds
block Text — paragraphs, headings (h1h6), blockquotes, list items.
image A picture with alt text, an optional rich caption, and asset references.
video An embedded or hosted video with provider metadata and a caption.
codeBlock A code listing with a language, an optional filename, and line highlighting.
table Rows and columns of rich-text cells, with header and cell-spanning support.
formula A mathematical expression as LaTeX, shown as a display equation or inline.
aside A callout — tip, warning, danger, note — with a rich inner body.

Building a parser or renderer?

This page is the friendly overview. For the complete developer reference — every block, span, mark, and custom type with its full JSON shape and field semantics — see PCF reference.

Text blocks can also carry annotations richer than a plain link: footnotes and endnotes (each with a full rich-text body), structured citations for a generated bibliography, cross-references to another block ("see Figure 3.1"), and editorial comments that never render in published output. Lists need no wrapper — consecutive text blocks tagged with a listItem type and a level form a list, and the renderer groups them.

A mixed fragment — heading, prose, and a code listing (JSON)

A realistic slice of a chapter: a heading, an introductory paragraph, and a code listing with a highlighted line. Note the prefixed, readable block keys.

[
  {
    "_type": "block",
    "_key": "sec-hello-heading",
    "style": "h2",
    "markDefs": [],
    "children": [
      { "_type": "span", "_key": "s1", "text": "A first server", "marks": [] }
    ]
  },
  {
    "_type": "block",
    "_key": "sec-hello-intro",
    "style": "normal",
    "markDefs": [],
    "children": [
      { "_type": "span", "_key": "s1", "text": "The ", "marks": [] },
      { "_type": "span", "_key": "s2", "text": "net/http", "marks": ["code"] },
      { "_type": "span", "_key": "s3", "text": " package ships a production-grade server in Go's standard library:", "marks": [] }
    ]
  },
  {
    "_type": "codeBlock",
    "_key": "sec-hello-listing",
    "language": "go",
    "code": "package main\n\nimport \"net/http\"\n\nfunc main() {\n\thttp.ListenAndServe(\":8080\", nil)\n}",
    "filename": "main.go",
    "highlightLines": [6],
    "showLineNumbers": true
  }
]
An aside with an internal link (JSON)

An aside callout whose body links to another Content Lake Resource using an internalLink annotation. Internal references use the cl:// scheme — never a raw web URL — so the link stays valid as content is versioned and moved.

[
  {
    "_type": "aside",
    "_key": "note-see-chapter-9",
    "tone": "note",
    "title": "Authentication moved",
    "body": [
      {
        "_type": "block",
        "_key": "note-body-1",
        "style": "normal",
        "markDefs": [
          {
            "_type": "internalLink",
            "_key": "ch9-link",
            "reference": "cl://packt/document/res_ml_go_ch9/latest#sec-oauth"
          }
        ],
        "children": [
          { "_type": "span", "_key": "s1", "text": "The login flow is now covered in ", "marks": [] },
          { "_type": "span", "_key": "s2", "text": "Chapter 9", "marks": ["ch9-link"] },
          { "_type": "span", "_key": "s3", "text": ".", "marks": [] }
        ]
      }
    ]
  }
]

The reference is a cl:// URI of the form cl://{tenant}/{type}/{resourceId}/{version}#{blockKey} — here it points at block sec-oauth in Resource res_ml_go_ch9 at the working state latest. See the Glossary for the cl:// scheme.

Name versus fingerprint: _key and blockHash

Every block carries two identifiers, and keeping them straight is worth a moment because they do opposite jobs.

How the two identifiers differ (name vs fingerprint)

Think of a block as a person. Its _key is its name — a stable, public handle assigned once and kept for life. Its blockHash is its fingerprint — derived entirely from what the block currently contains, and it changes the instant the content changes.

_key (the name) blockHash (the fingerprint)
What it is A stable, human-friendly handle A content-derived hash
Stays the same when You edit the block's text Never — it tracks the content
Changes when Never (assigned once, kept for life) Any word, link, or mark changes
Used for Addressing: links, entity anchors, citations point to (resource, _key) Deduplication, "provably unchanged" checks, skip-processing enrichment

Because the _key is stable, an internal link or an entity annotation that points at a block keeps working after the block is edited — the name has not changed, only the words. Because the blockHash tracks content, the platform can tell at a glance whether two blocks are identical, and can avoid re-running expensive enrichment on a block whose fingerprint it has already seen.

A _key is a short, URI-safe handle — the platform generates twelve-character random keys, though any name matching the grammar in the conformance specification is accepted. It must be unique within its document, since links address blocks by it. The blockHash is a SHA-256 hash written as sha256: followed by hexadecimal; its exact canonicalisation rules are normative and defined in the conformance specification.

The examples on this page use readable keys such as intro-heading and sec-hello-listing for clarity. In production the platform assigns random keys, but the principle is identical: the name is stable, the fingerprint follows the content.

The normative reference

This page is a business-facing introduction. It deliberately does not restate the exact rules an implementation must follow — the _key grammar, the blockHash canonicalisation algorithm and its test vector, character-offset counting, the structural validity errors, and the size limits. Those rules are authoritative and live in the platform's internal PCF specification, so that two independent implementations produce byte-identical results.

Authoritative source

The conformance detail — identity, key grammar, offsets, validity, and limits — is normative and maintained in the platform PCF specification, not on this site. Where this page and the specification ever appear to differ, the specification wins. Builders integrating against PCF should treat the conformance document and its JSON Schemas as the contract.

Two facts from that specification are worth carrying away, because they shape how PCF behaves in practice:

  • Strict about structure, open about vocabulary. Anything that would corrupt identity or addressing — a missing key, a duplicate key, a link pointing at nothing — is rejected on write. But unknown block types and unknown fields are accepted and preserved, so the format can grow without breaking older content.
  • Empty is valid. An empty document, an empty paragraph, and an empty span are all legitimate — drafts start empty and fill in over time. Emptiness is a content state, not an error.
  • PCF reference — the complete developer reference: every block, span, mark, and custom type with its JSON.
  • The Content Lake — the store PCF content lives in, and how it fits the wider platform.
  • Resources & versions — how PCF documents are held on a Resource and snapshotted into versions.
  • Enrichment & the Knowledge Graph — how the entity overlay annotates PCF blocks by position without touching the content.
  • Search — how blocks are chunked and indexed for retrieval.
  • Core concepts — where Resources, Works, and the rest of the vocabulary fit together.
  • Explorer — Chapter IV — the lake and the content format, visualised.