Visual Editing in Astro with Sanity Presentation Tool
Click-to-edit overlays, live preview, and real-time collaboration without building a custom preview system. Here's how Sanity's Presentation Tool works with Astro.
April 13, 2026
The problem with traditional preview workflows
Most headless CMS setups force editors into a loop: make a change, save it, wait for a build or API refresh, then switch tabs to see the result. If the change doesn't look right, repeat the whole cycle.
Custom preview routes help, but they add maintenance overhead. They often fall out of sync with production rendering, showing editors a version of the page that doesn't match what visitors will see.
The core issue is that editors lose context. They're working in a form-based interface that looks nothing like the final page, making it hard to judge spacing, tone, and visual hierarchy.
How Presentation Tool works with Astro
Sanity's Presentation Tool takes a different approach. Instead of building a separate preview system, it uses your actual Astro frontend as the editing surface.
Here's how it works:
- Iframe embedding — The Presentation Tool loads your Astro site in an iframe inside Sanity Studio, with a side-by-side content editing panel. Editors see the real page, not a simulation.
- Stega encoding — The
@sanity/astrointegration invisibly annotates rendered text with source metadata: document IDs, field paths, and content types. This encoding is invisible to visitors but readable by the Presentation Tool. - Click-to-edit overlays — The tool reads stega annotations to create interactive overlays. Editors click any text on the page and the corresponding field opens in the editing panel.
- Live mode — Draft changes stream to the frontend in real time. Editors see updates as they type, not after they save.
Setting it up in this template
This template has visual editing support built in. The @sanity/astro integration is configured in astro.config.mjs with the Studio URL for overlay linking:
sanity({
stega: {
studioUrl: "/studio",
},
}) Example: Enabling Visual Editing
---
import { PortableText } from "astro-portabletext";
import { createDataAttribute } from "@sanity/visual-editing-csm";
import { getPost } from "../../utils/sanity";
const { slug } = Astro.params;
const post = await getPost(slug!);
const dataAttr = createDataAttribute({
projectId: import.meta.env.PUBLIC_SANITY_STUDIO_PROJECT_ID,
dataset: import.meta.env.PUBLIC_SANITY_STUDIO_DATASET,
baseUrl: import.meta.env.PUBLIC_SANITY_STUDIO_URL,
id: post._id,
type: post._type,
});
---
<div data-sanity={dataAttr?.scope("body").toString()}>
<PortableText value={post.body} />
</div>