Visual Editing

Stega-encoding

Learn about Stega, an encoding method for embedding metadata in apps, enabling click-to-edit features in Sanity's Visual Editing tools.

Stega (from “steganography) is an encoding method developed by Sanity in collaboration with Vercel that allows metadata mappings to be embedded into an application. It encodes Content Source Maps, a standard for annotating fragments in a JSON document with metadata about their origin.

With Stega enabled, data rendered in your application looks the same but contains invisible metadata that Sanity's Visual Editing tooling can detect. This enables click-to-edit overlays directly in your application without requiring you to manually set up mapping for components.

Gotcha

How Stega works

Stega-encoding takes the Content Source Maps JSON and encodes it into invisible UTF-8 encoded characters that are appended to a string value. The Visual Editing tooling can pick this up, decode it, and use for the click-to-edit functionality:

Oxford Shoes​​​​‌‍​‍​‍‌‍‌​‍‌‍‍‌‌‍‌‌‍‍‌‌‍‍​‍​‍​‍‍​‍​‍‌​‌‍​‌‌‍‍‌‍‍‌‌‌​‌‍‌​‍‍‌‍‍‌‌‍​‍​‍​‍​​‍​‍‌‍‍​‌​‍‌‍‌‌‌‍‌‍​‍​‍​‍‍​‍​‍‌‍‍​‌‌​‌‌​‌​​‌​​‍‍​‍​‍‌‌‍‌‍‍‌‌​‌‌‌‌‍​‌‌‍​​‍‌‌‍‌‌‌‍‌​‌‍‍‌‌‌​‌‍‍‌‌‍‍‌‍‌​‍‌‌​‌‌​‌‌‌‌‍‌​‌‍‍‌‌‍​‍‍‌​‌‍​‌‌‍‍‌‍‍‌‌‌​‌‍‌​‍‍‌‍​‍‌‌‌‌‍‍‌‌‍​‌‍‌​​‍‌​‍‌‍‌‌‌‍‌‌‍‍‌‌‍​​‍‌‍‍‌‌‍‍‌‌​‌‍‌‌‌‍‍‌‌​​‍‌‍‌‌‌‍‌​‌‍‍‌‌‌​​‍‌‍‌‌‍‌‍‌​‌‍‌‌​‌‌​​‌​‍‌‍‌‌‌​‌‍‌‌‌‍‍‌‌​‌‍​‌‌‌​‌‍‍‌‌‍‌‍‍​‍‌‍‍‌‌‍‌​​‌‌‍​‍​‍‌​‌‌​‌‍‌‍​‌‍‌‍​‌‌‍​‍​‍‌‌‍​‌​‌‌‍​‍​‌​‍‌​‌​‌‍‌‌​‌‌​‌‍​‍‌‌‍​‌​​‌​‌‍‌‍‌‍​‍‌‌‍​‍‌‍​‌‌‍‌​​‍​‌‍‌‌‌‍‌‌​​​‍‌​​‍‌‍‌‌​​​‌​​‍‌‌​‌‍‌‌​​‌‍‌‌​‌‌​‌‍‍​‌‍‌‍‌‌​‍‌​​‌‍​‌‌‌​‌‍‍​​‌‌‌​‌‍‍‌‌‌​‌‍​‌‍‌‌​‍‌‌​‌‍‌‍‌‍​​‌‌​​‌​‍‌‍‌‌‌​‌‍‌‌‌‍‍‌‌​‌‍​‌‌‌​‌‍‍‌‌‍‌‍‍​‌‍​‍‌‍​‌‌​‌‍‌‌‌‌‌‌‌​‍‌‍​​‌‌‍‍​‌‌​‌‌​‌​​‌​​‍‌‌​​‌​​‌​‍‌‌​​‍‌​‌‍​‍‌‌​​‍‌​‌‍‌‌‍‌‍‍‌‌​‌‌‌‌‍​‌‌‍​​‍‌‌‍‌‌‌‍‌​‌‍‍‌‌‌​‌‍‍‌‌‍‍‌‍‌​‍‌‌​‌‌​‌‌‌‌‍‌​‌‍‍‌‌‍​‍‍‌​‌‍​‌‌‍‍‌‍‍‌‌‌​‌‍‌​‍‍‌‍​‍‌‌‌‌‍‍‌‌‍​‌‍‌​​‍‌‍‌‍‍‌‌‍‌​​‌‌‍​‍​‍‌​‌‌​‌‍‌‍​‌‍‌‍​‌‌‍​‍​‍‌‌‍​‌​‌‌‍​‍​‌​‍‌​‌​‌‍‌‌​‌‌​‌‍​‍‌‌‍​‌​​‌​‌‍‌‍‌‍​‍‌‌‍​‍‌‍​‌‌‍‌​​‍​‌‍‌‌‌‍‌‌​​​‍‌​​‍‌‍‌‌​​​‌​​‍‌‍‌‌​‌‍‌‌​​‌‍‌‌​‌‌​‌‍‍​‌‍‌‍‌‌​‍‌‍‌​​‌‍​‌‌‌​‌‍‍​​‌‌‌​‌‍‍‌‌‌​‌‍​‌‍‌‌​‍‌‍‌‌‌‍‌​‍‌‍‍‌​‌​​‌‍​‌‌‍​‌‍‌‌​‌‌​‍‌‍‌‌‌‍‌‌‍‍‌‌‍​​‍‌‍‌‌​‌‍‌‍‌‍​​‌‌​​‌​‍‌‍‌‌‌​‌‍‌‌‌‍‍‌‌​‌‍​‌‌‌​‌‍‍‌‌‍‌‍‍​‍‌‍‌‍‍‌‌​‌​‌​‌​‍‌‍​‌‌‍‌‍‌‌​​‌​‍​‍‌‌

What is Content Source Maps?

Content Source Maps is a standard representation for annotating fragments in a JSON document with metadata about their origin: the field, document, and dataset they originated from. It provides a separate document alongside the content that adds the metadata without changing the original document's layout.

Content Source Maps enable annotating JSON documents with "source" metadata, allowing end users to navigate directly to the source to edit it. In the future, they will also enable the annotation of JSON documents with arbitrary metadata for other use cases.

The Content Source Map offers a standard method for representing the mapping between content values and their sources. Here is an example Content Source Map:

{
  "documents": [
    {
      "_id": "author-1"
    },
    {
      "_id": "author-2"
    }
  ],
  "paths": ["$['name']"],
  "mappings": {
    "$[0]": {
      "type": "value",
      "source": {
        "type": "documentValue",
        "document": 0,
        "path": 0
      }
    },
    "$[1]": {
      "type": "value",
      "source": {
        "type": "documentValue",
        "document": 1,
        "path": 0
      }
    }
  }
}

Additional client configuration options for handling Content Source Maps

Log encoded paths to the console

The enhanced Sanity client accepts an optional logger parameter. Pass it the global console, to show debug information about which fields are being encoded and which (if any) are skipped.

const client = createClient({
  projectId: '<projectId>',
  dataset: 'production',
  apiVersion: '2022-05-03',
  useCdn: true,
  stega: {
    logger: console,
    studioUrl: '/studio',
  }
})

This should provide a nicely formatted report in your console, whether you are looking at your browser’s dev tools or your hosting's logs.

[@sanity/preview-kit]: Creating source map enabled client
[@sanity/preview-kit]: Stega encoding source map into result
  [@sanity/preview-kit]: Paths encoded: 3, skipped: 17
  [@sanity/preview-kit]: Table of encoded paths
  ┌─────────┬──────────────────────────────────┬───────────────────────────┬────────┐
  │ (index) │                     path         │           value           │ length │
  ├─────────┼──────────────────────────────────┼───────────────────────────┼────────┤
  │    0    │ ["footer",0,"children",0,"text"] │ '"The future is alrea...' │   67   │
  │    1    │ ["footer",1,"children",0,"text"] │     'Robin Williams'      │   14   │
  │    2    │             ["title"]            │     'Visual Editing'      │   14   │
  └─────────┴──────────────────────────────────┴───────────────────────────┴────────┘
  [@sanity/preview-kit]: List of skipped paths [
    [ 'footer', number, '_key' ],
    [ 'footer', number, 'children', number, '_key' ],
    [ 'footer', number, 'children', number, '_type' ],
    [ 'footer', number, '_type' ],
    [ 'footer', number, 'style' ],
    [ '_type' ],
    [ 'slug', 'current' ],
  ]

Customizing which paths to encode

The filter callback can be used to make a custom selection of which paths to encode and which to skip.

You may wish to skip encoding on a path if its value is used in your front end, but it is not useful for an author to be able to edit it.

const client = createClient({
  // ...rest of config omitted for brevity
  stega: {
    filter: (props) => {
      // custom filter logic
      // return true to include stega
      // return false to omit from stega encoding
      switch (props.sourceDocument._type) {
       case 'icon':
         return false
       default:
         return props.filterDefault(props)
      }
    }
  }
})

By default, all returned values will include metadata encoding unless they:

  • have keys starting with an underscore (e.g., _id and _type)
  • can be evaluated as a URL or ISO Date
  • are not returned as a string (such as numbers, see Encoding metadata on number fields below)
  • contain the path ["slug", "current"] (as the metadata encoding will break URLs and static build processes).

If you want keys like these to be included, you can override the default behavior and force a true return instead of returning props.filterDefault(props).

const client = createClient({
  // ...rest of config omitted for brevity
  stega: {
    filter: () => true
  }
})

Include the Content Source Map in your query result

To have the underlying Content Source Map returned as part of your query result, set the filterResponse option of the fetch call to false.

You may choose to include the Source Map for your own custom logic.

const client = createClient({
  // ...rest of config omitted for brevity
  apiVersion: '2026-01-27',
  resultSourceMap: true // Tells Content Lake to include content source maps in the response
})

// const result = await client.fetch(query, params)
const {result, resultSourceMap} = await client.fetch(
  query,
  params,
  {filterResponse: false} // This option returns the entire API response instead of selecting just `result`
)

doSomethingAwesome(resultSourceMap)

Was this page helpful?