# 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:

1.  [**Draft Mode**](https://www.datocms.com/docs/nuxt/include-draft-contents-during-development.md) — Access unpublished content during preview sessions
    
2.  [**Real-time Updates**](https://www.datocms.com/docs/nuxt/real-time-updates.md) — See content changes reflected immediately without page refresh
    
3.  **Content Link** — Click-to-edit overlays that connect frontend elements to their CMS fields
    
4.  [**Web Previews Plugin**](https://www.datocms.com/marketplace/plugins/i/datocms-plugin-web-previews.md) — 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.

(Video content)

Click-to-edit overlays

##### 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
    

(Video content)

Side-by-side editing

## 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.

(Image content)

Content Link overlays

##### How it works

1.  **Stega encoding** — When fetching draft content, pass the `contentLink` and `baseEditingUrl` options to embed invisible metadata into text fields
    
2.  **Detection** — The `<ContentLink />` component scans your page for this encoded content
    
3.  **Overlays** — Interactive overlays appear when editors hover over editable content
    
4.  **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 [`composables/useQuery.ts`](https://github.com/datocms/nuxt-starter-kit/blob/main/composables/useQuery.ts) for the full implementation):

```javascript
import { buildRequestInit } from '@datocms/cda-client';

useFetch('https://graphql.datocms.com/', {
  ...buildRequestInit(query, {
    token: apiToken,
    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 [`components/ContentLink`](https://github.com/datocms/nuxt-starter-kit/blob/main/components/ContentLink/index.vue) and [`app.vue`](https://github.com/datocms/nuxt-starter-kit/blob/main/app.vue) for implementation details):

```html
<template>
  <ClientOnly>
    <ContentLink v-if="draftMode" />
  </ClientOnly>
</template>
```

For more advanced use cases — like building a custom toolbar to toggle edit mode, or programmatically triggering the "flash all editable elements" animation — use the [`useContentLink` composable](https://github.com/datocms/vue-datocms/blob/master/src/components/ContentLink/README.md#advanced-usage-the-usecontentlink-composable):

```javascript
import { useContentLink } from 'vue-datocms';

const { enableClickToEdit, disableClickToEdit, flashAll } = useContentLink();
```

For component props and keyboard shortcuts, see the [vue-datocms ContentLink documentation](https://github.com/datocms/vue-datocms/blob/master/src/components/ContentLink/README.md#props).

##### 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:

```html
<template>
  <div data-datocms-content-link-group>
    <StructuredText :data="content.body" />
  </div>
</template>
```

**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 `renderLinkToRecord` does **not** need a boundary — record links are just `<a>` tags wrapping text that belongs to the surrounding structured text, so there's no URL collision.

```javascript
import { h } from 'vue';

const renderBlock = ({ record }) => {
  return h('div', { 'data-datocms-content-link-boundary': '' }, [
    h(ImageBlockComponent, { block: record })
  ]);
};

const renderInlineRecord = ({ record }) => {
  return h('a', { href: `/team/${record.slug}`, 'data-datocms-content-link-boundary': '' }, record.firstName);
};

const renderLinkToRecord = ({ record, children, transformedMeta }) => {
  return h('a', { ...transformedMeta, href: `/team/${record.slug}` }, children);
};

const renderInlineBlock = ({ record }) => {
  return h('code', { 'data-datocms-content-link-boundary': '' }, `@${record.username}`);
};
```

See the [ContentLink Structured Text documentation](https://github.com/datocms/vue-datocms/blob/master/src/components/ContentLink/README.md#structured-text-fields) for details.

## Web Previews Plugin (optional enhancement)

The [(Image content)Web Previews](https://www.datocms.com/marketplace/plugins/i/datocms-plugin-web-previews.md) 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.

(Image content)

Side-by-side editing in DatoCMS

##### 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 [`server/api/preview-links/index.ts`](https://github.com/datocms/nuxt-starter-kit/blob/main/server/api/preview-links/index.ts) for the full implementation):

server/api/preview-links/index.ts

```javascript
export default eventHandler(async (event) => {
  const { item, locale } = await readBody(event);
  const url = recordToWebsiteRoute(item, locale);

  return {
    previewLinks: [{ label: 'Draft', url: `/api/draft-mode/enable?redirect=${url}` }]
  };
});
```

**Enable Draft Mode route** — Activates draft mode and redirects to the preview. This is the same route [covered in the Draft Mode guide](https://www.datocms.com/docs/nuxt/include-draft-contents-during-development.md).

##### Configuring the plugin

In your DatoCMS project:

1.  Install the [Web Previews plugin](https://www.datocms.com/marketplace/plugins/i/datocms-plugin-web-previews.md) from the marketplace
    
2.  Configure a frontend with:
    
    -   **Preview Links API endpoint**: `https://yoursite.com/api/preview-links?token=your-secret`
        
    -   **Enable Draft Mode route**: `https://yoursite.com/api/draft-mode/enable?token=your-secret`
        

For full configuration details, see the [Web Previews plugin documentation](https://www.datocms.com/marketplace/plugins/i/datocms-plugin-web-previews.md#installation-and-configuration).

> [!WARNING] Content Security Policy
> 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 Nuxt, this is typically configured via the `routeRules` option or a server middleware:
> 
> nuxt.config.ts
> 
> ```javascript
> export default defineNuxtConfig({
>   routeRules: {
>     '/**': {
>       headers: {
>         'Content-Security-Policy': "frame-ancestors 'self' https://plugins-cdn.datocms.com"
>       }
>     }
>   }
> });
> ```

## Related content in "Nuxt"

- [Nuxt + DatoCMS Overview](https://www.datocms.com/docs/nuxt.md)
- [Include draft contents](https://www.datocms.com/docs/nuxt/include-draft-contents-during-development.md)
- [Responsive images](https://www.datocms.com/docs/nuxt/managing-images.md)
- [Displaying videos](https://www.datocms.com/docs/nuxt/displaying-videos.md)
- [Structured Text fields](https://www.datocms.com/docs/nuxt/rendering-structured-text-fields.md)
- [Adding SEO to Nuxt pages](https://www.datocms.com/docs/nuxt/seo-management.md)
- [Real-time updates](https://www.datocms.com/docs/nuxt/real-time-updates.md)
- [Visual Editing](https://www.datocms.com/docs/nuxt/visual-editing.md)
- [Nuxt Starter Kit    
Words are nice... but code speaks louder. Dive into a fully commented project template,
            showcasing these techniques (and more) in action.
       ✅ Official   Nuxt 4](https://www.datocms.com/marketplace/starters/nuxt-starter-kit.md)