Configuring the Portable Text editor
How to configure the editor for Portable Text
Portable Text is a JSON specification built on the idea of rich text as an array of blocks, themselves arrays of children spans. You can use it instead of Markdown or MDX in order to store your content in a presentation agnostic way, that can be transformed (or “serialized”) in to pretty much any markup language or framework.
Portable Text is extendible. Each block can have a style and a set of mark definitions, which describe data structures distributed on the children spans. Portable Text also allows for inserting arbitrary data objects in the array, only requiring _type
-key. Portable Text also allows for custom content objects in the root array, enabling editing- and rendering environments to mix rich text with custom content types.
You can create as many versions of the editor for Portable Text as you want. A frequent pattern is to have one simple configuration restricted to a few decorators and annotations, and a more comprehensive configuration with custom block types. Perhaps editors only should be able to use emphasis and annotate text as internal links in some settings (e.g. a caption) but have a full toolset in another (e.g an article body).
The minimal configuration to get Portable Text, and the editor in Sanity Studio looks like this:
export default {
name: 'content',
type: 'array',
title: 'Content',
of: [
{
type: 'block'
}
]
}
This will render the rich text editor with a default configuration for styles, decorators, and annotations.
Although Portable Text is markup agnostic, we have set the default configuration to map easily with HTML conventions. Bold and italics will set the decorators strong
and em
(emphasis), and produce a data structure like this:
[
{
"_type": "span",
"_key": "eab9266102e81",
"text": "strong",
"marks": [
"strong"
]
},
{
"_type": "span",
"_key": "eab9266102e82",
"text": " and ",
"marks": []
},
{
"_type": "span",
"_key": "eab9266102e83",
"text": "emphasis.",
"marks": [
"em"
]
}
]
As you probably can see, Portable Text isn't designed for direct human authoring or reading, but to be easily parsed by software. The _type
-key will also make it queryable in Sanity’s APIs, and by other tools for JSON like jq.
Since Portable Text defines rich text as an array, adding custom content blocks for images, videos, or code-embeds, just means inserting these in between the paragraph blocks. For Sanity’s editor for Portable Text, these have to be object-like types, and not primitive types like string
, number
, or boolean
.
Gotcha
You can also use types that are installed with plugins. Remember that some plugins, like some of the ones for tables, may export an array type. Since you can't store arrays directly within arrays, you have to wrap these in an object first.
To add images to Portable Text, append a new type object to the array as such:
export default {
name: 'content',
type: 'array',
title: 'Content',
of: [
{
type: 'block'
},
{
type: 'image'
}
]
}
This configuration will add an insert-menu, with an image as the only option:
Selecting an image will insert the block with a preview in the editor for Portable Text. It can be dragged and dropped to a new position, or edited by double-clicking the preview box or by using the edit button:
The Portable Text data structure for this example looks like the following:
[
{
"style": "normal",
"_type": "block",
"markDefs": [],
"_key": "09cc5f099d3b",
"children": [
{
"_type": "span",
"_key": "09cc5f099d3b0",
"text": "Kokos is a miniature schnauzer.",
"marks": []
}
]
},
{
"_type": "image",
"_key": "a5e9155ee3f5",
"asset": {
"_type": "reference",
"_ref": "image-61991cfbe9182124c18ee1829c07910faadd100e-2048x1366-png"
}
},
{
"style": "normal",
"_type": "block",
"markDefs": [],
"_key": "54145e9cb006",
"children": [
{
"_type": "span",
"_key": "54145e9cb0060",
"text": "Kokos is a good dog!",
"marks": []
}
]
}
]
The image has its own object structure, with an asset with a reference to the asset’s document. While the image URL can be derived from its _id
(the value for _ref
in this snippet), the asset document can also be joined in using select in GROQ:
*[_type == "post"]{
...,
content[]{
...,
_type == "image" => {
...,
asset->
}
}
}
In our documentation, we use a lot of code blocks. These are also just custom blocks that we added to our editor. You can install the code input as a plugin using the Sanity CLI:
sanity install @sanity/code-input
Once installed you can add the code block to the editor for Portable Text configuration:
export default {
name: 'content',
type: 'array',
title: 'Content',
of: [
{
type: 'block'
},
{
type: 'image'
},
{
type: 'code'
}
]
}
"Code" (you can change this by adding title: "your title"
to the same object) will now appear as a selection in the insert menu. Inserting a code block will give you a preview, and a code editor:
There are more options you can set for the code input.
Out of the box, the editor will give you the styles normal
, h1–6
, and blockquote
. These are set to map to HTML, but a style can be an arbitrary value.
// The default set of styles
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
styles: [
{title: 'Normal', value: 'normal'},
{title: 'H1', value: 'h1'},
{title: 'H2', value: 'h2'},
{title: 'H3', value: 'h3'},
{title: 'H4', value: 'h4'},
{title: 'H5', value: 'h5'},
{title: 'H6', value: 'h6'},
{title: 'Quote', value: 'blockquote'}
]
}
]
}
If you want to tie Portable Text to specific use cases, that is certainly possible: Maybe you are using Sanity to work with content that will be transformed into InDesign’s XML-format where you have ready-made templates with their own style names. Or perhaps your organization has a BEM-based CSS design system, and you want to embed certain class names in the rich text data.
We usually would recommend keeping the configuration on a reasonably abstract level and follow some known conventions. If you plan to use our Portable Text tooling for rendering web pages, you should probably stay closer to naming conventions in HTML.
To override the default configuration for styles, add the style
-key and set an array of title
/value
-objects:
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
styles: [
{ title: 'Normal', value: 'normal' },
{ title: 'Heading 2', value: 'h2' },
{ title: 'Quote', value: 'blockquote' },
{ title: 'Hidden', value: 'blockComment' }
]
}
]
}
Here we have set 4 possible styles. The 3 first are from the default settings and will be parsed in HTML to <p>
, <h2>
, and <blockquote>
. The fourth blockComment
is just an arbitrary style that we set because we plan to make it possible for editors to visually hide some blocks of text from rendering, but still have it available in the source code as a block comment.
Want to style the blocks in the editor? Read more →
The editor supports two types of lists: Bullet and Number. If your block
type doesn't contain a lists
definition, your editor will display both a bulleted list option and a numbered list option:
The default is the equivalent of explicitly naming both:
// The default set of styles
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
lists: [
{title: 'Bullet', value: 'bullet'},
{title: 'Numbered', value: 'number'}
] // yes please, both bullet and numbered
}
]
}
You can override the default by naming the lists you want. If you leave the array empty, you disable lists all together:
// The default set of styles
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
lists: [] // no lists, thanks
}
]
}
Also, you decide what goes into the title: {title: 'Prioritized', value: 'number'}
works equally well!
Marks is how we mark up inline text with additional data. Marks comes in two forms: Decorators and Annotations. Decorators are marks as simple string values, while Annotations are keys to a data structure. Annotations is a powerful feature of Portable Text in combination with Sanity’s backend because it allows you to embed complex data structures and references in running text.
Decorators works similarly to styles, but are applied to spans, that is, inline text. The default configurations are strong
, em
, code
, underline
, and strike-through
. If you want to disable some of these and set your own, you do that by adding an array to the decorators
key, under marks
:
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
marks: {
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
{title: 'Code', value: 'code'}
]
}
}
]
}
Decorators end up as icons in the toolbar. This configuration looks like this:
Annotations make it possible to embed rich content structures on top of inline text. A simple and frequent annotation can be a reference to another document, typically used for internal linking. A complete example with frontend implementation can be found at sanity-io/sanity-recipes on Github. To add an internal link annotation, configure the Portable Text schema like this:
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
marks: {
decorators: [
// ...
],
annotations: [
{
name: 'internalLink',
type: 'object',
title: 'Internal link',
fields: [
{
name: 'reference',
type: 'reference',
title: 'Reference',
to: [
{ type: 'post' },
// other types you may want to link to
]
}
]
}
]
}
}
]
}
Gotcha
If you plan to use Sanity’s GraphQL API, you should hoist internalLink
as a schema type, and use type: 'internalLink'
as the annotation instead of the anonymous object in the example above.
The annotations will appear in the toolbar with a Sanity S icon if none is set (see the chapter on customization for how to change the icons):
The data structure will look like this in Portable Text:
[
{
"_key": "da9dc50335a0",
"_type": "block",
"children": [
{
"_key": "da9dc50335a00",
"_type": "span",
"marks": [
"5b86c1132a66"
],
"text": "This is an internal link"
},
{
"_key": "da9dc50335a01",
"_type": "span",
"marks": [],
"text": "."
}
],
"markDefs": [
{
"_key": "5b86c1132a66",
"_type": "internalLink",
"reference": {
"_ref": "1dfa4e95-9f92-4e13-901b-1a769724e23c",
"_type": "reference"
}
}
],
"style": "normal"
}
]
The Portable Text editor supports a modal
option with type
and width
properties, letting you customize the size of your annotation popovers.
export default {
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
marks: {
annotations: [
{
type: 'object',
name: 'link',
title: 'Link',
options: {
// 👇👇👇
modal: {
type: 'popover', // 'dialog' (default) | 'popover' | 'fullscreen'
width: 'large', // 'small' (default) | 'medium' | 'large' | 'full'
},
// 👆👆👆
},
},
],
},
// ...
},
],
}
The width
property is ignored if type
is set to fullscreen
.