Warning

This article is for an older version of Sanity Studio (v2), which is deprecated. Learn how to migrate to Studio v3 →

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.

Add custom blocks

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.

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

Activated insert menu with image 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:

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 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->
    }
  }
}

Example: Code input

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:

The code editor with some schema code in JavaScript

There are more options you can set for the code input.

Configuring styles for text blocks

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.

The default style configuration in the editor

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 →

Editor with style configuration

Configuring lists for text blocks

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:

Bullet and numbered lists

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!

Configuring marks for inline text

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

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:

Toolbar with custom decorator configuration

Annotations

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):

Reference modal for internal link annotation

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"
  }
]

Customizing annotation popovers

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.