Unlock seamless workflows and faster delivery with our latest releases - Join the deep dive

Configuring the Portable Text editor

Set up and configure the Portable Text editor with decorators, annotations, and block content

Portable Text is a JSON specification built on the idea of rich text as an array of blocks, where each block is an array of child spans. You can use it instead of Markdown or MDX to store your content in a presentation-agnostic way. This allows transforming, or serializing, content into any markup language or framework.

Portable Text is extendible. Each block can have a style and a set of mark definitions that describe data structures distributed in the child spans. Portable Text also enables inserting arbitrary data objects in the array, only requiring a _type-key.
Portable Text allows 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 a simple configuration with selected decorators and annotations, and a more comprehensive configuration with custom block types.
This can help if editors can only use emphasis and annotate text as internal links in some settings (for example, a caption) and have a full toolbox available in another (for example, an article body).

Minimal configuration example

The following example shows a minimal configuration to implement Portable Text and the editor in Sanity Studio:

export default {
  name: 'content',
  type: 'array',
  title: 'Content',
  of: [
    {
      type: 'block'
    }
  ]
}

The code renders the Portable Text editor with a default configuration for styles, decorators, and annotations.

Portable Text is markup-agnostic; however, the default configuration maps easily to HTML conventions. Bold and italics 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"
    ]
  }
]

Portable Text isn't designed for direct human authoring or reading, but to be easily parsed by software. The _type-key also makes it queryable in Sanity’s APIs, and by other JSON tools, such as jq or groq-js.

Add custom blocks

Since Portable Text defines block content as an array, adding custom content blocks for images, videos, or code-embeds, means inserting these items between paragraph blocks.

Gotcha

Content blocks for the Portable Text editor must be object-like types, and not primitive types like string, number, or boolean.

You can also use types that are installed with plugins.
Some plugins, such as those for tables, may export an array type. Since it's not possible to store arrays directly within other arrays, you first need to wrap them in an object.

Example: images

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 Image as the only option:

The insert image image option is added to the toolbar

Selecting an image inserts the block with a preview in the Portable Text editor. You can drag the image and drop it in its designated position; you can also edit it by double-clicking the preview box or by selecting the edit option from the kebab menu:

Edit or delete a custom block

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 object structure, where asset references the asset’s document. While you can derive the image URL from its _id (corresponding to the _ref value in the code above), you can also join the asset document using select in GROQ:

*[_type == "post"]{
  ...,
  content[]{
    ...,
    _type == "image" => {
      ...,
      asset->
    }
  }
}

Example: code input

Our documentation features many code blocks. They are custom blocks that we added to our editor. You can install the code input as a plugin using the Sanity CLI:

npm i @sanity/code-input
# OR
yarn add @sanity/code-input

Once installed and configured, 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 (to change the default title to a different one, add title: "<my-custom-title>" to the same object) is now available as a selection in the insert menu. Inserting a code block produces a preview and a code editor:

The code editor with some schema code in JavaScript

You can set more options for the code input.

Configuring styles for text blocks

Out of the box, the Portable Text editor includes the following styles: normal, h1–h6, and blockquote. By default, they 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 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 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.

The default style configuration in the editor

We recommend keeping the configuration to a reasonably abstract level and following known, established 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 are parsed in HTML to <p>, <h2>, and <blockquote>.

blockComment is an arbitrary style that we set because we plan to make it possible for editors to hide selected blocks of text from rendering, while keeping them available in the source code as block comments.

Want to style the blocks in the editor? Read more →

Editor with style configuration

Configuring lists for text blocks

The editor supports two types of lists: bullet (unordered) and number (ordered). If your block type doesn't contain a lists definition, your editor features both a bullet list and a numbered list option:

Bullet and numbered lists

The default is the equivalent of explicitly naming both:

You can override the default by naming the lists you want. If you leave the array empty, you disable lists altogether:

// 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
    }
  ]
}
// 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!

Configuring marks for inline text

Portable Text enables marks to label inline text with additional data. There are two types of marks: decorators and annotations.
Decorators are marks as simple string values, while annotations are keys to a data structure. Annotations are a powerful feature of Portable Text in combination with Sanity’s backend, because they allow embedding complex data structures and references in running text.

Decorators

Decorators work similarly to styles, but they 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 are displayed as icons in the toolbar. This configuration looks like this:

Toolbar with custom decorator configuration

Annotations

Annotations enable embedding rich content data in inline text. An example can be a reference to another document, typically used for internal linking. A complete guide with front-end implementation can be found here.

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 example above.

Learn more about using GraphQL with Sanity here.

The annotations are displayed in the toolbar as question mark icons if none is set.
For more information on editing toolbar icons, see Customizing the Portable Text editor.

Reference modal for internal link annotation

The corresponding Portable Text data structure looks like this:

[
  {
    "_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"
  }
]

Further reading

For more information about decorators, annotations, and customizing block content, see:

Was this article helpful?