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.

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 block.children.map(child => child.text).join('')
    })
    // join the parapgraphs leaving split by two linebreaks
    .join('\n\n')
}

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>',
  useCdn: true
})

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

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

Serialization tooling

We have helpers for this for different languages and platforms.

Protip

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 www.portabletext.org.

Portable Text to HTML

https://github.com/sanity-io/block-content-to-html

Portable Text to React

https://github.com/sanity-io/block-content-to-react

Portable Text to Vue

https://github.com/rdunk/sanity-blocks-vue-component

Portable Text to Hyperscript

https://github.com/sanity-io/block-content-to-hyperscript

Portable Text to Markdown

https://github.com/sanity-io/block-content-to-markdown

Portable Text in .NET

https://github.com/oslofjord/sanity-linq#9-rendering-block-content

Portable Text in PHP

https://github.com/sanity-io/sanity-php#rendering-block-content

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.

Deserialization

If you need to convert existing markup to Portable Text you can 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 => field.name === 'body').type


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


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