Visual Editing
Visual Editing represents the ultimate content management experience — the "holy grail" for content editors. Instead of navigating through forms and fields in a CMS interface, editors can see their content exactly as it appears on the live site, click directly on any element to edit it, and watch changes appear instantly.
This seamless experience is achieved by combining several techniques that work together:
Draft Mode — Access unpublished content during preview sessions
Real-time Updates — See content changes reflected immediately without page refresh
Content Link — Click-to-edit overlays that connect frontend elements to their CMS fields
Web Previews Plugin — The DatoCMS plugin that orchestrates the editing experience
This guide focuses on Content Link and Web Previews — the final pieces that transform a preview into a true visual editing environment.
Two levels of integration
Visual Editing can be set up incrementally:
Level 1: Content Link (standalone)
With just Content Link configured, editors browsing your website in draft mode can click on any content element to edit it. Clicking opens DatoCMS in a new browser tab, navigating directly to the field that controls that content.
This works entirely on your website — no DatoCMS plugin required. It's a great starting point that already provides significant value to editors.
Level 2: Web Previews Plugin (side-by-side)
Adding the Web Previews plugin takes it further: editors can now view the website and DatoCMS interface side-by-side within DatoCMS itself. When they click on content, the edit panel opens instantly in the same view — no tab switching required.
The plugin also enables:
Preview links in the DatoCMS sidebar
Bidirectional navigation (browse the preview, and DatoCMS follows along)
Full-screen Visual Editing mode
Content Link: Click-to-edit overlays
Content Link enables the "click-to-edit" functionality by embedding invisible metadata (called "stega encoding") into your content. When editors hover over content in draft mode, visual overlays appear indicating which elements are editable.
This works entirely on your website — editors simply browse the site in draft mode, and clicking any editable element opens DatoCMS in a new tab. No plugin installation required.
How it works
Stega encoding — When fetching draft content, pass the
contentLinkandbaseEditingUrloptions to embed invisible metadata into text fieldsDetection — The
<ContentLink />component scans your page for this encoded contentOverlays — Interactive overlays appear when editors hover over editable content
Deep linking — Clicking an element opens DatoCMS at the exact field that controls that content
Setting up Content Link
The setup involves two parts:
Enable stega encoding when fetching draft content by passing the contentLink and baseEditingUrl options (see src/lib/datocms/queries.ts for the full implementation):
executeQuery(query, { includeDrafts: true, contentLink: 'v1', baseEditingUrl: 'https://your-project.admin.datocms.com',});Add the <ContentLink /> component to your root layout, rendered only in draft mode (see src/lib/components/ContentLink and src/routes/+layout.svelte for implementation details):
<script> import { ContentLink } from '@datocms/svelte'; import { goto } from '$app/navigation'; import { page } from '$app/stores';</script>
{#if data.draftModeEnabled} <ContentLink onNavigateTo={(path) => goto(path)} currentPath={$page.url.pathname} enableClickToEdit={{ hoverOnly: true }} />{/if}The SvelteKit integration uses goto from $app/navigation for client-side navigation and $page.url.pathname to track the current path — this enables the Web Previews plugin to stay in sync with your preview.
For component props and keyboard shortcuts, see the @datocms/svelte ContentLink documentation.
Working with Structured Text
Structured Text fields require two rules for Visual Editing to work correctly.
Rule 1: Always wrap the Structured Text component in a group. This makes the entire structured text area clickable, instead of just the tiny stega-encoded span:
<div data-datocms-content-link-group> <StructuredText data={content.structuredText} /></div>Rule 2: Wrap embedded blocks, inline records, and inline blocks in a boundary. These elements have their own edit URL (pointing to the block/record). Without a boundary, clicking them would bubble up to the parent group and open the structured text field editor instead. Note that record links (item links) do not need a boundary — their content belongs to the surrounding structured text, so there's no URL collision.
In your custom components, wrap the root element with data-datocms-content-link-boundary:
<script> export let block;</script>
<div data-datocms-content-link-boundary> <h2>{block.title}</h2> <p>{block.description}</p></div><script> export let block;</script>
<em data-datocms-content-link-boundary>{block.username}</em><script> export let link;</script>
<a href="/team/{link.slug}" data-datocms-content-link-boundary>{link.title}</a>Then pass them to StructuredText:
<script> import { StructuredText } from '@datocms/svelte'; import { isBlock, isInlineBlock, isInlineItem, isItemLink } from 'datocms-structured-text-utils'; import Block from './Block.svelte'; import InlineBlock from './InlineBlock.svelte'; import InlineItem from './InlineItem.svelte'; import ItemLink from './ItemLink.svelte';</script>
<div data-datocms-content-link-group> <StructuredText data={page.content} components={[ [isBlock, Block], [isInlineBlock, InlineBlock], [isInlineItem, InlineItem], [isItemLink, ItemLink], ]} /></div>See the ContentLink Structured Text documentation for details.
Web Previews Plugin (optional enhancement)
The
Web Previews plugin enhances the editing experience by embedding your website preview directly inside DatoCMS. Instead of switching between browser tabs, editors get a side-by-side view where clicking on content instantly opens the edit panel.
When Content Link detects it's running inside the Web Previews plugin iframe, it automatically switches from opening new tabs to communicating with the plugin — no code changes required.
How it works
The plugin communicates with your frontend through two API endpoints:
Preview Links API — Receives record info from DatoCMS and returns preview URLs (see src/routes/api/preview-links/+server.ts for the full implementation):
export const POST: RequestHandler = async ({ url, request }) => { const { item, itemType, locale } = await request.json(); const recordUrl = recordToWebsiteRoute(item, itemType.id, locale);
return json({ previewLinks: [ { label: 'Draft version', url: `/api/draft-mode/enable?redirect=${recordUrl}` } ] });};Enable Draft Mode route — Activates draft mode and redirects to the preview. This is the same route covered in the Draft Mode guide.
Configuring the plugin
In your DatoCMS project:
Install the Web Previews plugin from the marketplace
Configure a frontend with:
Preview Links API endpoint:
https://yoursite.com/api/preview-links?token=your-secretEnable Draft Mode route:
https://yoursite.com/api/draft-mode/enable?token=your-secret
For full configuration details, see the Web Previews plugin documentation.
If your website implements a Content Security Policy with a frame-ancestors directive, you need to allow the DatoCMS plugin to embed your site. In SvelteKit, this is typically configured via the handle hook in hooks.server.ts:
export const handle: Handle = async ({ event, resolve }) => { const response = await resolve(event);
response.headers.set( 'Content-Security-Policy', "frame-ancestors 'self' https://plugins-cdn.datocms.com" );
return response;};