Pricing update: Free users

Presenting Portable Text

Transform Portable Text to whatever you want

When you query your Sanity project’s API your rich text content is returned as Portable Text. If you are accustomed to traditional or other headless CMS you are probably used to dealing with HTML or Markdown right of the box. Portable Text is designed for being used in pretty much any format or markup where you want to render rich text content.

You render Portable Text by serializing the arrays with your content into the format you need it in. There is tooling for generic markup and programming languages, and for popular frameworks, that makes it easier to serialize Portable Text and lets you decide how custom content types should be dealt with.

Serialization tooling

We have helpers for this for different languages and platforms.


You may notice some mentions of block text, including in the tool names. This was the nomenclature we used before open sourcing and publishing the specification for Portable Text. You can explore the specification on

Portable Text to HTML

Portable Text to React

Portable Text to Vue

Portable Text to Svelte

Portable Text to Hyperscript

Portable Text to Markdown

Portable Text in .NET

Portable Text in Python

Portable Text in PHP

Need to serialize to something not listed here in a language we don't cover? Create an issue on the repo for Portable Text, or join us on Slack and let us know.

Plain text serialization

Serializing Portable Text to plain text can be useful when you need it previews or similar. It also helps demystify what goes into serializing Portable Text. Here's a function written in JavaScript that takes a Portable Text array as an argument, and returns it as paragraphs in plain text:

function toPlainText(blocks = []) {
  return blocks
    // loop through each block
    .map(block => {
      // if it's not a text block with children, 
      // return nothing
      if (block._type !== 'block' || !block.children) {
        return ''
      // loop through the children spans, and join the
      // text strings
      return => child.text).join('')
    // join the paragraphs leaving split by two linebreaks

Rendering Portable Text in React

A common use case is to render rich text content from Sanity in the popular web framework React. We have made tooling that deals with the defaults out of the box, and lets you add serializers for controlling how custom content type should be rendered in the frontend. Let's look at a simple example for how to set it up:

const React = require('react')
const ReactDOM = require('react-dom')
const BlockContent = require('@sanity/block-content-to-react')
const client = require('@sanity/client')({
  projectId: '<your project id>',
  dataset: '<some dataset>',
  apiVersion: '2021-08-26',
  useCdn: true

const serializers = {
  types: {
    code: props => (
      <pre data-language={props.node.language}>

client.fetch('*[_type == "article"][0]').then(article => {
    <BlockContent blocks={article.body} serializers={serializers} />,

Join references

If you have references to other documents such as internal links, files or images in your Portable Text, you typically want to join the data from those documents into your Portable Text. Here's how:

Say you have a Portable Text with internal links to other articles using mark annotations like so:

// portableText.js
export default {
  name: 'portableText',
  type: 'array',
  title: 'Content',
  of: [
      type: 'block',
      marks: {
        annotations: [
            name: 'internalLink',
            type: 'object',
            title: 'Internal link',
            fields: [
                name: 'reference',
                type: 'reference',
                title: 'Reference',
                to: [
                  { type: 'article' },
                  // other types you may want to link to

In order to get the slug of the linked article you need to use GROQ to query your document and use the join syntax to fetch the referenced article like so:

*[_type == "post"]{
      _type == "internalLink" => {
        "slug": @.reference->slug

Read the full guide on including links in Portable Text.


If you need to convert existing markup to Portable Text you can use the JavaScript library Sanity Block Tools. You can use it both in a browser and in a node.js environment. It also lets you make custom rules to deserialize parts of your HTML into custom content types etc.

Complete example of deserialization of HTML into Portable Text blocks in a browser environment:

import Schema from '@sanity/schema'
import blockTools from '@sanity/block-tools'

// Start with compiling a schema we can work against
const defaultSchema = Schema.compile({
  name: 'myBlog',
  types: [
      type: 'object',
      name: 'blogPost',
      fields: [
          title: 'Title',
          type: 'string',
          name: 'title'
          title: 'Body',
          name: 'body',
          type: 'array',
          of: [{type: 'block'}]

// The compiled schema type for the content type that holds the block array
const blockContentType = defaultSchema.get('blogPost')
  .fields.find(field => === 'body').type

// Convert HTML to block array
const blocks = blockTools.htmlToBlocks(
  '<html><body><h1>Hello world!</h1><body></html>',
// Outputs
//  {
//    _type: 'block',
//    style: 'h1'
//    children: [
//      {
//        _type: 'span'
//        text: 'Hello world!'
//      }
//    ]
//  }

// Get the feature-set of a blockContentType
const features = blockTools.getBlockContentFeatures(blockContentType)

Was this article helpful?