Introduction
What is Portable Text?
Section titled “What is Portable Text?”Portable Text is a JSON-based specification for structured block content. Instead of storing content as an HTML string or Markdown, Portable Text represents it as an array of typed blocks: each block has a _type and carries its own data.
┌─────────────────────────────────────────┐│ Portable Text document ││ (array of blocks) ││ ││ ┌─ _type: block ────────────────────┐ ││ │ style: "h1" │ ││ │ "Why Portable Text?" │ ││ └───────────────────────────────────┘ ││ ┌─ _type: block ────────────────────┐ ││ │ style: "normal" │ ││ │ "Read the **docs** for details." │ ││ │ └─ annotation: link (href, ...) │ ││ └───────────────────────────────────┘ ││ ┌─ _type: image ────────────────────┐ ││ │ url: "photo.jpg" │ ││ │ alt: "A mountain landscape" │ ││ │ caption: "View from the summit" │ ││ └───────────────────────────────────┘ ││ ┌─ _type: block ────────────────────┐ ││ │ style: "normal" │ ││ │ "Current price: [stockTicker] ." │ ││ │ └─ inline: stockTicker │ ││ │ symbol: "AAPL" │ ││ │ exchange: "NASDAQ" │ ││ └───────────────────────────────────┘ ││ ┌─ _type: callToAction ─────────────┐ ││ │ text: "Start building" │ ││ │ url: "/getting-started" │ ││ └───────────────────────────────────┘ │└─────────────────────────────────────────┘Text blocks, image blocks, and custom blocks like a call-to-action all live in the same array. A serializer walks the array and renders each block based on its type.
Three levels of extensibility
Section titled “Three levels of extensibility”Portable Text is a block content format, not just a rich text format. Rich text (formatted paragraphs with bold, italic, and links) is one type of block among many. The format supports three levels of custom content:
Block level Custom block types sit alongside text blocks (images, code blocks, CTAs, embeds, tables) ─────────────────────────────────────────────Inline level Inline objects sit inside text blocks (stock tickers, product refs, custom emoji) ─────────────────────────────────────────────Mark level Annotations carry data on text spans (links with tracking, refs with doc IDs, comments, footnotes)Custom blocks are any _type you define. An image block carries url, alt, and caption. A code block carries language and source. A CTA carries text and a link. The serializer renders each one through a component you provide.
Inline objects are structured data embedded in the text flow. A stock ticker inside a paragraph carries symbol and exchange data. A product reference carries a product ID. They’re not text with formatting; they’re data that happens to appear within text.
Annotations are data-carrying marks on text spans. A link annotation isn’t just an <a> tag: it’s a data object with href, target, tracking parameters, or whatever fields you define. You can query “find all documents that link to /pricing” because the link data is structured JSON, not buried in an HTML string.
How rendering works
Section titled “How rendering works”A serializer converts the block array into your target format. The same Portable Text renders as React components, HTML strings, Markdown, PDFs, or plain text:
┌──────────────┐ ┌──→│ React │──→ <Article>...</Article> │ └──────────────┘┌───────────┐ │ ┌──────────────┐│ Portable │───┼──→│ HTML │──→ <div>...</div>│ Text JSON │ │ └──────────────┘└───────────┘ │ ┌──────────────┐ ├──→│ Markdown │──→ # Hello **world** │ └──────────────┘ │ ┌──────────────┐ └──→│ PDF / email │──→ (any format) └──────────────┘Default block types (paragraphs, headings, lists, bold, italic, links) work out of the box. Custom blocks and annotations need a component for each type. If a serializer encounters a type it doesn’t recognize, it skips it. Nothing breaks.
Why structured data?
Section titled “Why structured data?”Because content is JSON, you can:
- Render anywhere. Pass the same data to any serializer. Each renders what it can, ignores what it can’t.
- Query the content. “Find all blocks that contain a link to /pricing” is a JSON query, not a regex over HTML.
- Extend without breaking. Add new block types, inline objects, or annotation types. Renderers that don’t recognize them skip them gracefully.
- Validate the structure. The schema is explicit. You know exactly what types of content exist and what data they carry.
Learn why Portable Text over HTML, Markdown, or Gutenberg →
Get started
Section titled “Get started”There are two ways to work with Portable Text: