Index
Edit

Configuration

Portable Text is built on the idea of rich text as an array of blocks, themselves arrays of children spans. 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 some 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 the Content 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.

The default editor configuration

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.

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:

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 embed certain classnames 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 to 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.

Editor with style configuration

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 datastructures 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, emphasis, code, underline and strikethrough. 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 ends up as icons in the toolbar. This configuration looks like this:

Toolbar with custom decorator configuration

Annotations

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

Next: Introduction