Setting Up Portable Text for Rich Content in Sanity
Portable Text is a format for storing rich text in Sanity. It's a JSON structure, not HTML: blocks with types, markers, annotations, embedded objects. Same data renders as HTML, React Native, PDF through respective serializers.
Portable Text Schema
// schemas/blockContent.ts
import { defineArrayMember, defineType } from 'sanity'
export const blockContentType = defineType({
name: 'blockContent',
type: 'array',
of: [
defineArrayMember({
type: 'block',
styles: [
{ title: 'Normal', value: 'normal' },
{ title: 'H2', value: 'h2' },
{ title: 'Quote', value: 'blockquote' },
],
marks: {
decorators: [
{ title: 'Bold', value: 'strong' },
{ title: 'Italic', value: 'em' },
{ title: 'Code', value: 'code' },
],
},
}),
defineArrayMember({
type: 'image',
options: { hotspot: true },
fields: [{ name: 'alt', type: 'string' }],
}),
defineArrayMember({
type: 'object',
name: 'callout',
title: 'Callout',
fields: [
{ name: 'type', type: 'string', options: { list: ['info', 'warning', 'tip'] } },
{ name: 'text', type: 'text' },
],
}),
],
})
Rendering via @portabletext/react
npm install @portabletext/react
// components/PortableTextContent.tsx
import { PortableText } from '@portabletext/react'
import type { PortableTextComponents } from '@portabletext/react'
const components: PortableTextComponents = {
types: {
image: ({ value }) => (
<img src={value.url} alt={value.alt} className="my-4 rounded-lg" />
),
},
marks: {
strong: ({ children }) => <strong className="font-bold">{children}</strong>,
link: ({ value, children }) => (
<a href={value?.href} className="text-blue-600">{children}</a>
),
},
}
export function PortableTextContent({ value }: { value: any[] }) {
return <PortableText value={value} components={components} />
}
Extract Plain Text
import { toPlainText } from '@portabletext/toolkit'
const plainText = toPlainText(post.body)
const excerpt = plainText.slice(0, 160)
Timeline
Setting up Portable Text schema with custom blocks and renderer — 1–2 days.







