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 CMSes you are probably used to dealing with HTML or Markdown out of the box. Portable Text is designed to be used in pretty much any format or markup where you want to render rich text content.
You render Portable Text by serializing the arrays that contain your content into the format you need it. 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 handled.
We have helpers 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.
https://github.com/portabletext/to-html
https://github.com/portabletext/react-portabletext
https://github.com/rdunk/sanity-blocks-vue-component
https://github.com/portabletext/svelte-portabletext/
https://github.com/theisel/astro-portabletext
https://github.com/sanity-io/block-content-to-hyperscript
https://github.com/sanity-io/block-content-to-markdown
https://github.com/oslofjord/sanity-linq#9-rendering-block-content
https://github.com/otovo/python-portabletext-html
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.
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 paragraphs leaving split by two linebreaks
.join('\n\n')
}
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 components for controlling how custom content type should be rendered in the front end. Let's look at an example of how to set it up:
import React from 'react'
import * as ReactDOM from 'react-dom'
import {PortableText} from '@portabletext/react'
import {createClient} from '@sanity/client'
const client = sanityClient({
projectId: '<your project id>',
dataset: '<your dataset>',
apiVersion: '2022-05-05',
useCdn: true
})
const components = {
types: {
code: props => (
<pre data-language={props.node.language}>
<code>{props.node.code}</code>
</pre>
)
}
}
client.fetch('*[_type == "article"][0]')
.then(article => {
ReactDOM.render(
<PortableText value={article.body} components={components} />,
document.getElementById('root')
)
})
Additional details are available in our documentation or on the @portabletext/react
README.
Portable Text to React
In 2022, the @sanity/block-content-to-react
package was deprecated in favour of @portabletext/react
. The example above uses components and values from the new package instead of serializers and blocks, but we offer a migration guide and will continue to answer questions about @sanity/block-content-to-react
in the Slack community.
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"]{
...,
body[]{
...,
markDefs[]{
...,
_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 => 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)