When should `_key` be generated in Sanity block-tools HTML deserializer?

15 replies
Last updated: Dec 2, 2025
Looking at your tests for `block-tools`https://github.com/sanity-io/sanity/tree/master/packages/%40sanity/block-tools/test/tests/HtmlDeserializer
What are the conditions that should create a
_key
? From this source https://github.com/portabletext/portabletext#markdefs-array it sounds like it is only used with
markDefs
, but in the test folder,
randomKey
s are inserted everywhere!Also I created this example
https://codesandbox.io/s/wizardly-dhawan-2iq4w?file=/input.html Where there are keys, but running this locally, there are no keys
AI Update

Great question! This confusion comes from the difference between the Portable Text specification (the open standard) and Sanity's implementation requirements for storing Portable Text.

When _key is Actually Required

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:

In the Portable Text Specification

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.

In Sanity's Implementation

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:

  • Each block in the blocks array
  • Each item in the markDefs array
  • Potentially on child spans (though the spec doesn't mandate this)
  • Any custom inline objects

This is why the block-tools tests add randomKey everywhere - they're testing the Sanity-ready output format, not the minimal Portable Text spec.

Why Your CodeSandbox Has No Keys

The htmlToBlocks function from @sanity/block-tools may or may not generate _key properties depending on:

  1. Schema context: The function needs a proper Sanity schema to understand it should add _key properties
  2. Version differences: Different versions may have different default behaviors
  3. Configuration: You may need to explicitly configure key generation

Here'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)
  }))
}))

Bottom Line

  • Portable Text spec: _key is mainly for markDefs references
  • Sanity's implementation: _key is required on all objects in arrays for collaboration and tracking
  • block-tools tests: Show the Sanity-ready format with keys everywhere
  • Your code: May need explicit schema context or manual key generation to match Sanity's requirements

The tests are correct for Sanity usage - if you're importing data into Sanity, you'll want those _key properties throughout.

Show original thread
15 replies

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.

Was this answer helpful?