
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeGreat question! This confusion comes from the difference between the Portable Text specification (the open standard) and Sanity's implementation requirements for storing Portable Text.
According to the Sanity documentation on _key, the _key property is required for objects within arrays in Sanity documents. This is a Sanity-specific requirement for real-time collaborative editing and proper document tracking.
However, this doesn't mean _key is required on every object in Portable Text according to the spec itself. Let me clarify:
Looking at the official Portable Text spec on GitHub, _key is primarily used in the markDefs array where it serves as a reference identifier. The spec shows span objects like this:
{
"_type": "span",
"marks": ["a-key", "emphasis"],
"text": "some text"
}Notice there's no _key on the span itself in the spec example.
When you're storing Portable Text in Sanity's Content Lake, the broader array requirement kicks in. Since Portable Text blocks are stored as arrays, and those blocks contain arrays of children, Sanity requires _key on:
markDefs arrayThis is why the block-tools tests add randomKey everywhere - they're testing the Sanity-ready output format, not the minimal Portable Text spec.
The htmlToBlocks function from @sanity/block-tools may or may not generate _key properties depending on:
_key propertiesHere's a proper setup that should generate keys:
import {htmlToBlocks, randomKey} from '@sanity/block-tools'
import {Schema} from '@sanity/schema'
const defaultSchema = Schema.compile({
name: 'default',
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(htmlString, blockContentType, {
parseHtml: html => new DOMParser().parseFromString(html, 'text/html')
})If keys still aren't being generated automatically, you can add them manually:
import {randomKey} from '@sanity/block-tools'
const ensureKeys = (blocks) => blocks.map(block => ({
...block,
_key: block._key || randomKey(12),
markDefs: block.markDefs?.map(def => ({
...def,
_key: def._key || randomKey(12)
})),
children: block.children?.map(child => ({
...child,
_key: child._key || randomKey(12)
}))
}))_key is mainly for markDefs references_key is required on all objects in arrays for collaboration and trackingThe tests are correct for Sanity usage - if you're importing data into Sanity, you'll want those _key properties throughout.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store