Setting Up Sanity Visual Editing with the Presentation Tool

This guide, part of Sanity Studio Customization, sets up visual editing: the frontend shown inside Sanity Studio’s Presentation tool, with overlays that let editors click any piece of content to open the field that holds it, and live updates as they type. It covers the Studio configuration, the frontend’s draft mode, content source maps with stega encoding, live refresh and the security rules that keep preview machinery out of production.

Form-based editing forces editors to translate between fields and the page: which field is the subtitle under the hero, where does this card’s text come from. Visual editing removes that translation. It works by having the frontend render draft content with invisible markers, stega-encoded content source maps, that tell the overlay which document and field produced each string. The Presentation tool reads the markers, draws the overlays and opens the right field on click.

How a click becomes an open fieldThe Presentation tool loads the frontend in an iframe through a draft-mode route; the frontend fetches drafts with the viewer token and stega-encoded source maps; the overlay script finds encoded strings in the rendered page; an editor clicks a heading and the Studio opens the matching document and field.Presentation toolFrontend (draft mode)Content Lakeload /api/draft-mode/enabledrafts + source maps (viewer token)content with stega markersoverlays map stringsto document + patheditor clicks headingopen doc 42, field title
The encoding exists only in draft mode; production HTML contains none of it.

The Problem

A travel publisher’s editors previewed articles in a separate browser tab, switched back to the Studio to change a sentence, saved, and reloaded the preview, often losing their scroll position in long articles. Finding which field held a particular card title on a destination page took several minutes for new editors. Corrections that should take seconds took much longer, and editors avoided small improvements.

How Visual Editing Works

Presentation tool in the Studio. Add the presentationTool plugin with the frontend’s preview URL and an endpoint that enables draft mode. The tool shows the frontend in an iframe next to a document panel.

Draft mode on the frontend. A route validates a secret issued by the Studio and enables draft mode. In draft mode, the frontend queries with the drafts perspective, the viewer token and without caching.

Stega-encoded source maps. The Sanity client can return content source maps and embed them into strings as invisible characters. The overlay script decodes them to map rendered text to document ids and field paths. Enable stega only in draft mode.

Live updates. A live-content component subscribes to changes and refreshes the page data as editors type, so the preview reflects edits within moments without manual reloads.

Settings in draft mode and in productionFor the perspective, token, caching, stega encoding, overlays and live updates, the setting used in draft mode inside the Presentation tool and in production.SettingDraft modeProductionPerspectivedraftspublishedTokenviewer, server onlynone or read-onlyCachingbypassedCDN + tagsStega encodingonoffOverlays + live updatesonnot loaded
Every visual editing feature is switched on by draft mode and off otherwise.

Implementation

In the Studio, register the Presentation tool with the preview origin and the draft-mode endpoint.

TypeScript
// sanity.config.ts (excerpt)
import { presentationTool } from "sanity/presentation";

export default defineConfig({
  // ...
  plugins: [
    structureTool(),
    presentationTool({
      previewUrl: {
        origin: process.env.SANITY_STUDIO_PREVIEW_ORIGIN,        // e.g. https://www.example.com
        previewMode: { enable: "/api/draft-mode/enable" },
      },
    }),
  ],
});

On the frontend, with next-sanity, a route enables draft mode after validating the Studio’s request, and the client turns on stega encoding in draft mode only.

TypeScript
// app/api/draft-mode/enable/route.ts
import { defineEnableDraftMode } from "next-sanity/draft-mode";
import { client } from "@/lib/sanity/client";

export const { GET } = defineEnableDraftMode({
  client: client.withConfig({ token: process.env.SANITY_VIEWER_TOKEN }),
});
TypeScript
// lib/sanity/fetch.ts
import { draftMode } from "next/headers";
import { client } from "./client";

export async function sanityFetch<T>(query: string, params: Record<string, unknown> = {}, tags: string[] = []) {
  const isDraft = (await draftMode()).isEnabled;
  return client.fetch<T>(query, params, isDraft
    ? { perspective: "drafts", useCdn: false, stega: { enabled: true, studioUrl: "/studio" }, token: process.env.SANITY_VIEWER_TOKEN, cache: "no-store" }
    : { perspective: "published", useCdn: true, stega: false, next: { tags } });
}

In the root layout, render the visual editing component only in draft mode, so its script never loads for readers:

TSX
// app/layout.tsx (excerpt)
import { draftMode } from "next/headers";
import { VisualEditing } from "next-sanity";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const isDraft = (await draftMode()).isEnabled;
  return (
    <html lang="en">
      <body>
        {children}
        {isDraft && <VisualEditing />}
      </body>
    </html>
  );
}

For live updates, newer next-sanity versions provide a live-content setup that pairs fetching and subscriptions; enable it in draft mode the same way, with the viewer token only on the server and a browser token limited to draft viewing if the setup requires one.

Stega and non-display strings

Stega markers are invisible characters appended to strings, which is harmless in text but breaks values used as data: URLs, CSS class names, dates parsed by code, or strings compared in logic. Exclude such fields from encoding with the client’s stega filter, or clean values with stegaClean before using them as data. Metadata tags such as the page title also need cleaning, since they end up in the document head.

Mapping locations to documents

The Presentation tool can also show, for a document open in the Studio, where it appears on the site: which pages use this author, which landing pages include this product. Configure document locations with a resolver that, for each document type, queries the pages that reference it and returns their titles and URLs. Editors then open an author and jump straight to any page that shows them, which is particularly useful for shared content such as banners, legal notices and reusable blocks, where a change affects many pages at once. Keep the resolver’s queries light and limited to a handful of results; its purpose is orientation, not a complete usage report, and it runs every time an editor opens a document.

Configuration Reference

Setting Recommendation Why
Draft route validated by the Studio’s secret Only the Studio can enable draft mode.
Stega enabled only in draft mode Production HTML stays clean.
Stega filter exclude URLs, slugs, dates, enums Encoded data breaks logic.
Viewer token server-side only Drafts are not public.
Framing CSP frame-ancestors includes the Studio origin Presentation loads the site in an iframe.
Overlays loaded only in draft mode No preview script for readers.

Gotchas & Edge Cases

  • Frame blocking. A strict frame-ancestors or X-Frame-Options header prevents the Presentation tool from loading the site. Allow the Studio’s origin for draft routes, and nothing else.
  • Third-party cookies. Draft mode relies on cookies inside an iframe from another origin; they must be SameSite=None; Secure.
  • Strings used as keys. Encoded strings used as React keys or map keys cause mismatches; clean them first.
  • Server-rendered layouts cached in draft mode. Ensure every fetch path respects draft mode, including layouts and metadata, or parts of the preview show published data.

Worked Example

The travel publisher enabled the Presentation tool with draft mode, stega encoding filtered to exclude slugs and URLs, and live updates. Editors could click any heading, card or caption on a destination page to open the right field, and saw changes appear in the preview as they typed. The average time to complete a small correction, measured in a sample of editing sessions, fell from several minutes to under one, and new editors stopped needing a guide to the content model to find fields. Production pages were unaffected: a check of the public HTML confirmed that no stega characters or overlay scripts were present.

Time to complete a small correctionMedian time from finding a typo on a page to seeing the correction in preview, with form editing in a separate tab and with visual editing in the Presentation tool.Separate tab + forms210 secondsPresentation tool45 seconds
Click-to-edit removed the search for the right field and the reload cycle.

Performance and Scaling of Preview

Visual editing adds load that production does not see: every editor’s preview makes uncached draft queries, and live updates refetch as they type. For small teams this is negligible; for large editorial teams it can become noticeable in API usage and server load. Keep draft queries as trimmed as production ones, debounce live refetches, and make sure preview rendering does not trigger expensive work that production pages avoid through caching, such as generating Open Graph images or large sitemaps. Monitor API usage split by perspective, so growth in preview traffic is visible, and consider a separate preview deployment when editorial load is high, so preview traffic never competes with readers for server capacity.

Rollout Checklist

  • Add the Presentation tool with the preview origin and draft-mode endpoint.
  • Validate the Studio’s secret before enabling draft mode.
  • Fetch drafts with the viewer token, no caching and stega encoding in draft mode only.
  • Filter or clean stega from values used as data.
  • Load overlays and live updates only in draft mode.
  • Allow framing from the Studio origin for draft routes.

Frequently Asked Questions

Does visual editing replace form editing?

No. Editors still use forms for fields that are not visible, such as SEO settings. Visual editing makes visible content quick to find and change.

Can visual editing work without Next.js?

Yes. Sanity provides libraries for other frameworks; the principles of draft mode, source maps and overlays are the same.

Is stega safe for SEO?

It is only enabled in draft mode, so production pages and crawlers never see the markers at all.

Does visual editing work for localized content?

Yes. The source maps include the field path with the language, so clicking a German heading opens the German field. Load the preview in the locale the editor is currently working on.

What if a component renders content that is not from Sanity?

Only strings with source maps become editable. Other text renders normally without overlays, and editors simply cannot click on it.