Add Portable Text Editor plugins to Studio
Use official and community-built Portable Text Editor plugins in your studio.
Sanity Studio uses the standalone Portable Text Editor (PTE) to power the block content editing experience. This makes the editor aware of your schema, and also lets you modify the editor from your existing Sanity config and schema.
Because the PTE itself doesn't require Studio, there can be times where you need to include a PTE plugin that isn't set up to work with Studio.
This guide shows you how to use an existing PTE plugin with your studio.
Prerequisites:
sanityversion 3.92.0 or later is required for Studio to apply PTE plugins.- The PTE plugin must support the
@portabletext/editorversion that yoursanitypackage uses. To check, runnpm ls @portabletext/editorand compare the result with the plugin's@portabletext/editorpeer dependency. For example,@portabletext/plugin-character-pair-decorator9.x requires@portabletext/editor8.x.
Add the plugin to your project
The first step is to add the PTE plugin to your project. The examples in this guide use the CharacterPairDecoratorPlugin from the Portable Text Editor repository. It lets you define a Markdown-style shortcut and link it to a decorator.
npm install @portabletext/plugin-character-pair-decoratorpnpm add @portabletext/plugin-character-pair-decoratoryarn add @portabletext/plugin-character-pair-decoratorbun add @portabletext/plugin-character-pair-decoratorThe examples configure the plugin to make any text wrapped in # bold. For example, #example# receives the "strong" decorator.
Option 1: Add the plugin directly
The most straightforward approach is to import the plugin and add it directly to your studio config for global use, or your schema for specific component use.
Note the change to TSX
This approach requires that you change your files to TSX to accommodate JSX syntax. It means fewer files, but may not be your preferred technique. For alternatives, see Option 2 and Option 3.
Apply the plugin to every Portable Text field
This applies the plugin to all block content editors throughout your studio.
import { defineConfig } from 'sanity'
import { CharacterPairDecoratorPlugin } from '@portabletext/plugin-character-pair-decorator'
export default defineConfig({
// ... omitted for brevity
form: {
components: {
portableText: {
plugins: (props)=>{
return (
<>
{props.renderDefault(props)}
<CharacterPairDecoratorPlugin
decorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'strong')?.name
}
pair={{char: '#', amount: 1}}
/>
</>
)
},
}
},
}
})Apply the plugin to one field
This applies the plugin to a single block content field in your schema.
import { defineType } from 'sanity'
import { CharacterPairDecoratorPlugin } from '@portabletext/plugin-character-pair-decorator'
export const post = defineType({
title: 'Blog post',
name: 'post',
type: 'document',
fields: [
// ...
{
type: 'array',
name: 'content',
title: 'Post Body',
of: [
{
type: 'block',
}
],
components: {
portableText: {
plugins: (props)=>{
return (
<>
{props.renderDefault(props)}
<CharacterPairDecoratorPlugin
decorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'strong')?.name
}
pair={{char: '#', amount: 1}}
/>
</>
)
}
}
}
},
// ...
],
})Option 2: Create a container for plugins
This is the recommended approach. Wrap all editor plugins in a single container component, so your schema and configuration files don't need to change to TSX. For example, the guide on creating your own PTE plugin exports a PortableTextEditorPlugins function.
import type { PortableTextPluginsProps } from 'sanity'
import { CharacterPairDecoratorPlugin } from '@portabletext/plugin-character-pair-decorator'
export function PortableTextEditorPlugins(props: PortableTextPluginsProps) {
return (
<>
{props.renderDefault(props)}
<CharacterPairDecoratorPlugin
decorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'strong')?.name
}
pair={{char: '#', amount: 1}}
/>
{/* Add more plugins here */}
</>
)
}Then use that container globally in the sanity.config.ts, or locally in your schema.
import { defineConfig } from "sanity"
import { PortableTextEditorPlugins } from './plugins/pte'
export default defineConfig({
// ...
form: {
components: {
portableText: {
plugins: PortableTextEditorPlugins,
},
},
},
// ...
})import { defineType } from 'sanity'
import { PortableTextEditorPlugins } from './plugins/pte'
export const post = defineType({
title: 'Blog post',
name: 'post',
type: 'document',
fields: [
// ...
{
type: 'array',
name: 'content',
title: 'Post Body',
of: [
{
type: 'block',
}
],
components: {
portableText: {
plugins: PortableTextEditorPlugins,
}
}
},
// ...
],
})Option 3: Wrap in a Studio plugin
One approach is to wrap the PTE plugin in a Sanity Studio plugin using definePlugin, then add it to your config file as a plugin.
import { definePlugin } from 'sanity'
import { CharacterPairDecoratorPlugin } from '@portabletext/plugin-character-pair-decorator'
export const characterPair = definePlugin({
name: 'characterPair',
form: {
components: {
portableText: {
plugins: (props)=>{
return (
<>
{props.renderDefault(props)}
<CharacterPairDecoratorPlugin
decorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'strong')?.name
}
pair={{char: '#', amount: 1}}
/>
</>
)
},
}
},
}
})import { defineConfig } from 'sanity'
import { characterPair } from './plugins/characterPair'
export default defineConfig({
// ... omitted for brevity
plugins: [
//...
characterPair()
]
})If you package a plugin this way for distribution, expose its settings as plugin options so users can configure it in their sanity.config.ts. To do this, give definePlugin an options type, and return the plugin configuration from a function that receives those options:
import { definePlugin } from 'sanity'
import { CharacterPairDecoratorPlugin } from '@portabletext/plugin-character-pair-decorator'
interface CharacterPairOptions {
decorator?: string
char?: string
}
export const characterPair = definePlugin<CharacterPairOptions | void>((options) => {
// Defaults: the strong decorator and the # character
const { decorator = 'strong', char = '#' } = options || {}
return {
name: 'characterPair',
form: {
components: {
portableText: {
plugins: (props) => (
<>
{props.renderDefault(props)}
<CharacterPairDecoratorPlugin
decorator={({context}) =>
context.schema.decorators.find((d) => d.name === decorator)?.name
}
pair={{char, amount: 1}}
/>
</>
),
},
},
},
}
})import { defineConfig } from 'sanity'
import { characterPair } from './plugins/characterPair'
export default defineConfig({
// ... omitted for brevity
plugins: [
// Wrap text in _underscores_ to italicize it
characterPair({ decorator: 'em', char: '_' }),
],
})Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?