Explainer Needed: What is a Mark, Decorator and Serializer?
Great questions! These concepts can be confusing at first, but they're all part of how Portable Text works in Sanity. Let me break each one down:
1. What is a mark/custom mark?
A mark is any kind of formatting or metadata you apply to text spans within Portable Text. There are two types of marks:
- Decorators: Simple text formatting (bold, italic, underline, etc.)
- Annotations: Complex marks with additional data (links, footnotes, citations, etc.)
A custom mark is any mark you define yourself beyond the built-in ones. For example, you might create a custom decorator for highlighting text or a custom annotation for internal document references.
2. What is a decorator? Where is it used and what's its purpose?
A decorator is a simple string-based text formatting mark applied to inline text within Portable Text. Built-in decorators include:
- Bold (
strong) - Italic (
em) - Underline (
underline) - Strikethrough (
strike-through) - Code (
code)
Purpose: Decorators provide straightforward text styling within your rich text content. They're used in the Portable Text Editor when content editors need to format text.
3. What's the difference between a mark and a decorator?
- Mark is the umbrella term for any formatting applied to text
- Decorator is a specific type of mark that's simple and boolean (on/off)
- Annotation is the other type of mark that can carry complex data structures
Think of it like this: All decorators are marks, but not all marks are decorators. Annotations are marks that need additional data (like a URL for a link), while decorators just toggle formatting on or off.
4. What is a serializer? What's its purpose?
A serializer (now more commonly called a "component" in modern libraries) is a function that tells your frontend how to render Portable Text content. Since Portable Text is stored as JSON, you need serializers to transform that JSON into HTML, React components, or whatever format your frontend needs.
Purpose: Serializers bridge the gap between Sanity's structured content and your website's display layer.
5. Do I need to write a serializer for every new decorator?
Not necessarily!
- Built-in decorators (bold, italic, etc.) already have default serializers in libraries like @portabletext/react
- Custom decorators do need custom serializers to tell the frontend how to render them
Here's how to write a serializer for a custom decorator (like subscript):
import {PortableText} from '@portabletext/react'
const components = {
marks: {
sub: ({children}) => <sub>{children}</sub>,
sup: ({children}) => <sup>{children}</sup>
}
}
<PortableText value={portableTextContent} components={components} />6. Writing multiple serializers (code block + highlighted text)
You don't write two separate serializers—you write one serializer configuration object that handles both! Here's an example:
import {PortableText} from '@portabletext/react'
const components = {
types: {
// For custom block types like code blocks
code: ({value}) => (
<pre>
<code className={`language-${value.language}`}>
{value.code}
</code>
</pre>
)
},
marks: {
// For inline marks like highlighting
highlight: ({children}) => (
<span className="bg-yellow-200">{children}</span>
)
}
}
// In your OnePost.js component:
function OnePost({post}) {
return (
<article>
<h1>{post.title}</h1>
<PortableText
value={post.body}
components={components}
/>
</article>
)
}The components object handles all your custom rendering in one place:
typeshandles custom block-level elements (like code blocks, images, etc.)markshandles inline formatting (like your highlighted text)
Check out this comprehensive guide on customizing Portable Text for more examples!
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.