Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change Site Theme
Sanity logo

Documentation

    • Overview
    • Platform introduction
    • Next.js quickstart
    • Nuxt.js quickstart
    • Astro quickstart
    • React Router quickstart
    • Studio quickstart
    • Build with AI
    • Content Lake
    • Functions
    • APIs and SDKs
    • Visual Editing
    • Blueprints
    • Platform management
    • Dashboard
    • Studio
    • Canvas
    • Media Library
    • App SDK
    • Content Agent
    • HTTP API
    • CLI
    • Libraries
    • Specifications
    • Changelog
    • User guides
    • Developer guides
    • Courses and certifications
    • Join the community
    • Templates
Studio
Overview

  • Setup and development

    Installation
    Project Structure
    Development
    Hosting and deployment
    Embedding Sanity Studio
    Upgrading Sanity Studio
    Environment Variables
    Using TypeScript in Sanity Studio
    Understanding the latest version of Sanity

  • Configuration

    Introduction
    Workspaces
    Schema and forms
    Conditional fields
    Field Groups
    List Previews
    Connected Content
    Validation
    Initial Value Templates
    Cross Dataset References
    Sort Orders
    Visual editing and preview
    Incoming reference decoration

  • Block Content (Portable Text)

    Introduction
    Configure the Portable Text Editor
    Customize the Portable Text Editor
    Create a Portable Text behavior plugin
    Add Portable Text Editor plugins to Studio
    Common patterns
    Standalone Portable Text Editor

  • Studio customization

    Introduction
    Custom component for Sanity Studio
    Custom authentication
    Custom asset sources
    Diff components
    Form Components
    How form paths work
    Icons
    Favicons
    Localizing Sanity Studio
    New Document Options
    Studio Components
    Studio search configuration
    Focus and UI state in custom inputs
    Real-time safe patches for input components
    Sanity UI
    Studio Tools
    Create a custom Studio tool
    Tools cheat sheet
    Theming

  • Workflows

    The Dashboard tool for Sanity Studio
    Add widgets to dashboard
    Document actions
    Release Actions
    Custom document badges
    Localization
    Content Releases Configuration
    Enable and configure Comments
    Configuring Tasks
    Scheduled drafts
    Scheduled publishing (deprecated)
    Manage notifications

  • Structure builder

    Introduction
    Get started with Structure Builder API
    Override default list views
    Create a link to a single edit page in your main document type list
    Manually group items in a pane
    Dynamically group list items with a GROQ filter
    Create custom document views with Structure Builder
    Cheat sheet
    Structure tool
    Reference

  • Plugins

    Introduction
    Installing and configuring plugins
    Developing plugins
    Publishing plugins
    Internationalizing plugins
    Reference
    Official plugins repo

  • AI Assist

    Installation
    Translation
    Custom field actions
    Field action patterns

  • User guides

    Comments
    Task
    Copy and paste fields
    Compare document versions
    Content Releases
    Scheduled drafts
    View incoming references
    Common keyboard shortcuts

  • Studio schema reference

    Studio schema configuration
    Array
    Block
    Boolean
    Cross Dataset Reference
    Date
    Datetime
    Document
    File
    Geopoint
    Global Document Reference
    Image
    Number
    Object
    Reference
    Slug
    Span
    String
    Text
    URL

  • Studio reference

    Asset Source
    Configuration
    Document
    Document Badges
    Document Actions
    Form
    Form Components
    Hooks
    Structure tool
    Studio Components Reference
    Tools
    Initial Value Templates
    Studio API reference

On this page

Previous

Introduction

Next

Customize the Portable Text Editor

Was this page helpful?

On this page

  • Minimal configuration
  • Default behaviors
  • Markdown behaviors
  • Typographic behaviors
  • Map markdown behavior to custom marks and decorators
  • Add custom blocks
  • Example: images
  • Example: code input
  • Configuring styles for text blocks
  • Configuring lists for text blocks
  • Configuring marks for inline text
  • Decorators
  • Annotations
StudioLast updated January 9, 2026

Configure the Portable Text Editor

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

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. It also 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 base configuration with selected decorators and annotations, and a more comprehensive configuration with custom block types.
This is helpful if editors 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

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, an array of blocks, renders the Portable Text Editor with a default configuration for styles, decorators, and annotations.

Loading...

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 instead should be 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.

Default behaviors

Markdown behaviors

The Portable Text Editor in Studio includes many default Markdown behaviors that may be familiar from other text editors. They map to the standard decorators and annotations. These include:

  • # Title: One or more # symbols followed by a space and text renders heading levels.
  • > block quote content: The > character followed by a space and text converts to a block quote.
  • Backspace at the beginning of a block removes the styling.
  • -, *, _, or 1.: At the beginning of a line, these values will start a list.
  • <example>: Wrapping text in backticks, ` will create inline code.
  • *italic*: Wrapping text in single * characters will create italic text.
  • **bold**: Wrapping text in double ** characters will create bold text.

You can undo this behavior as it happens by backspacing after the change occurs.

Disable default markdown behavior

You can disable these markdown behaviors by modifying the configuration, either in your sanity.config.ts file globally, or on individual schema types.

export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              markdown: {
                enabled: false
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            markdown: {
              enabled: false
            }
          }
        })
      }
    }
  }
})

If you're already using custom PTE behavior plugins, you can add the contents of props.renderDefault above into the renderDefault call in the behavior plugin.

Typographic behaviors

Starting in Studio v4.22.0, the editor also includes a set of common typographic helpers. These transform the input inline, saving the transformed version to the document.

The following lists include the name of the behavior, used to enable/disable individual behaviors in the configuration, the input text, and the output text. Default behaviors are enabled for all Portable Text Editor fields unless disabled.

Default behaviors:

  • emDash: -- → —
  • ellipsis: ... → …
  • openingDoubleQuote: " → “
  • closingDoubleQuote: " → ”
  • openingSingleQuote: ' → ‘
  • closingSingleQuote: ' → ’
  • leftArrow: <- → ←
  • rightArrow: -> → →
  • copyright: (c) → ©
  • trademark: (tm) → ™
  • servicemark: (sm) → ℠
  • registeredTrademark: (r) → ®

Optional behaviors:

  • oneHalf: 1/2 → ½
  • plusMinus: +/- → ±
  • notEqual: != → ≠
  • laquo: << → «
  • raquo: >> → »
  • multiplication: * or x between numbers → ×
  • superscriptTwo: ^2 → ²
  • superscriptThree: ^3 → ³
  • oneQuarter: 1/4 → ¼
  • threeQuarters: 3/4 → ¾

Enable / disable typography behaviors

You can enable / disable typography behaviors at the global level, or directly in the schema definition for the block.

Explicitly disable all typography behaviors:

export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              typography: {
                enabled: false
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            typography: {
              enabled: false
            }
          }
        })
      }
    }
  }
})

Enable all presets (default and optional), set the preset key to:

  • default: enables the default behaviors. (this is the default setting if unset)
  • all: enables both default and optional behaviors
  • none: disables both default and optional behaviors. Use this if you plan to enable only specific behaviors (see below).
export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              typography: {
                preset: 'all'
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            typography: {
              preset: 'all'
            }
          }
        })
      }
    }
  }
})

Enable or disable individual behaviors with the enable or disable key. Each accepts an array of behavior names.

export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              typography: {
                enable: ['oneQuarter', 'threeQuarters'],
                disable: ['openingDoubleQuote', 'openingSingleQuote', 'closingDoubleQuote', 'closingSingleQuote']
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            typography: {
              enable: ['oneQuarter', 'threeQuarters'],
              disable: ['openingDoubleQuote', 'openingSingleQuote', 'closingDoubleQuote', 'closingSingleQuote']
            }
          }
        })
      }
    }
  }
})

Map markdown behavior to custom marks and decorators

If you're using non-standard names for your marks and decorators, you can map the default behaviors to the different names.

In this example, the unordered list is remapped to a list named "dot".

export default defineType({
  name: 'customBlock',
  // ... rest of config
  type: 'array',
  of: [
    {
      type: 'block',
      marks: {
        decorators: [/* ... */],
      },
      lists: [{ value: 'dot', title: 'Dot',},],
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            markdown: {
              ...props.plugins?.markdown?.config,
              unorderedList: ({schema}) =>
                schema.lists.find((list) => list.name === 'dot')?.name,
            }
          }
        })
      }
    }
  }
})

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:

Loading...
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:

Loading...
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. You can derive the image URL from its _id (which matches the _ref value 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:

Loading...
The code editor with some schema code in JavaScript

You can set more options for the code input.

How to add a custom YouTube block

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'}
      ]
    }
  ]
}
Loading...
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 →

Loading...
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:

Loading...
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:

Loading...
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.

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.

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

  • Article
  • Changelog
export default {
  name: 'content',
  type: 'array',
  title: 'Content',
  of: [
    {
      type: 'block'
    }
  ]
}
The default editor configuration
[
  {
    "_type": "span",
    "_key": "eab9266102e81",
    "text": "strong",
    "marks": [
      "strong"
    ]
  },
  {
    "_type": "span",
    "_key": "eab9266102e82",
    "text": " and ",
    "marks": []
  },
  {
    "_type": "span",
    "_key": "eab9266102e83",
    "text": "emphasis.",
    "marks": [
      "em"
    ]
  }
]
export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              markdown: {
                enabled: false
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            markdown: {
              enabled: false
            }
          }
        })
      }
    }
  }
})
export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              typography: {
                enabled: false
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            typography: {
              enabled: false
            }
          }
        })
      }
    }
  }
})
export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              typography: {
                preset: 'all'
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            typography: {
              preset: 'all'
            }
          }
        })
      }
    }
  }
})
export default defineConfig({
  // ... rest of config,
  form: {
    components: {
      portableText: {
        plugins: (props) => {
          return props.renderDefault({
            ...props,
            plugins: {
              ...props.plugins,
              typography: {
                enable: ['oneQuarter', 'threeQuarters'],
                disable: ['openingDoubleQuote', 'openingSingleQuote', 'closingDoubleQuote', 'closingSingleQuote']
              }
            }
          })
        }
      },
    },
  }
})
export default defineType({
  type: 'array',
  name: 'noMarkdown',
  title: 'No markdown',
  description: 'Markdown disabled in this PTE',
  of: [
    {
      type: 'block',
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            typography: {
              enable: ['oneQuarter', 'threeQuarters'],
              disable: ['openingDoubleQuote', 'openingSingleQuote', 'closingDoubleQuote', 'closingSingleQuote']
            }
          }
        })
      }
    }
  }
})
export default defineType({
  name: 'customBlock',
  // ... rest of config
  type: 'array',
  of: [
    {
      type: 'block',
      marks: {
        decorators: [/* ... */],
      },
      lists: [{ value: 'dot', title: 'Dot',},],
    },
  ],
  components: {
    portableText: {
      plugins: (props) => {
        return props.renderDefault({
          ...props,
          plugins: {
            ...props.plugins,
            markdown: {
              ...props.plugins?.markdown?.config,
              unorderedList: ({schema}) =>
                schema.lists.find((list) => list.name === 'dot')?.name,
            }
          }
        })
      }
    }
  }
})
export default {
  name: 'content',
  type: 'array',
  title: 'Content',
  of: [
    {
      type: 'block'
    },
    {
      type: 'image'
    }
  ]
}
The image option is added to the tool bar
Edit or delete a custom block
[
  {
    "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": []
      }
    ]
  }
]
*[_type == "post"]{
  ...,
  content[]{
    ...,
    _type == "image" => {
      ...,
      asset->
    }
  }
}
npm i @sanity/code-input
# OR
yarn add @sanity/code-input
export default {
  name: 'content',
  type: 'array',
  title: 'Content',
  of: [
    {
      type: 'block'
    },
    {
      type: 'image'
    },
    {
      type: 'code'
    }
  ]
}
The code editor with some schema code in JavaScript
// 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'}
      ]
    }
  ]
}
The default style configuration in the editor
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' }
      ]
    }
  ]
}
Editor with style configuration
The portable text editor UI with the list selectors highlighted
// 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
    }
  ]
}
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'}
        ]
      }
    }
  ]
}
Toolbar with custom decorator configuration
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
                ]
              }
            ]
          }
        ]
      }
    }
  ]
}
Reference modal for internal link annotation
[
  {
    "_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"
  }
]