Implementing a text editor in Sanity using a third-party tool and block-tools package.
I understand you want to implement a Portable Text editor directly on your client-side application, not just in Sanity Studio. You're right that much of the documentation focuses on rendering Portable Text, but there are options for creating editing experiences on the client side.
The Reality of Client-Side Portable Text Editing
The Portable Text Editor is indeed a standalone React-based editor that can be used outside of Sanity Studio. However, it's important to understand that this isn't a drop-in component - it requires significant integration work. You'll need to:
- Define your own schema for the content structure
- Create custom toolbars and UI controls
- Write render functions for blocks and annotations
- Handle state management and data persistence
- Implement any custom blocks you need
This is a lower-level library that gives you the editing engine, but you're responsible for building the complete editing experience around it.
Practical Alternatives
Given the complexity of implementing the Portable Text Editor from scratch, here are more practical approaches:
1. Use a Different Editor + Conversion Layer
Use a rich text editor you're comfortable with (like TipTap, Quill, or Slate) and convert the output to Portable Text format. The @sanity/block-tools package can help with this:
import {htmlToBlocks} from '@sanity/block-tools'
import {Schema} from '@sanity/schema'
// Define a simple schema
const defaultSchema = Schema.compile({
name: 'myBlog',
types: [
{
type: 'object',
name: 'blogPost',
fields: [
{
title: 'Body',
name: 'body',
type: 'array',
of: [{type: 'block'}]
}
]
}
]
})
const blockContentType = defaultSchema
.get('blogPost')
.fields.find(field => field.name === 'body').type
const blocks = htmlToBlocks('<p>Your HTML content</p>', blockContentType)The @sanity/block-tools package handles basic rich text formatting like headings, paragraphs, and lists automatically, and you can define custom deserialization rules for more complex HTML structures.
2. Build a Custom Form with Sanity Client
For simpler use cases, you might create a custom editing interface using standard form inputs and structure the data as Portable Text yourself. Then use @sanity/client to write it back:
import {createClient} from '@sanity/client'
const client = createClient({
projectId: 'your-project-id',
dataset: 'your-dataset',
token: 'your-write-token', // Keep this secure!
apiVersion: '2024-01-01',
useCdn: false
})
// Write Portable Text data
client
.patch('document-id')
.set({
body: [
{
_type: 'block',
children: [{_type: 'span', text: 'Your content here'}]
}
]
})
.commit()3. Embedded Studio
If you need full editing capabilities, consider embedding Sanity Studio itself into your application using an iframe or as a React component. This gives you the complete, battle-tested editing experience without reimplementing it.
Recommendation
For most use cases, I'd recommend using a familiar rich text editor (like TipTap) with @sanity/block-tools for conversion. This gives you:
- A polished, well-documented editing experience
- Less implementation complexity
- Standard Portable Text output that works with all Sanity features
Only invest in implementing the Portable Text Editor directly if you need deep customization of the editing experience and have the development resources to build and maintain the surrounding infrastructure.
The key is to remember that whatever approach you choose, as long as you output valid Portable Text JSON, it will work seamlessly with Sanity's rendering libraries and ecosystem.
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.