Skip to content

Visual Editor

Most CMSs require users to choose between integration and customization. Seamless integration with the CMS often means less freedom to customize your website, or vice versa.

Storyblok offers the best of both worlds: a WYSIWYG editing experience embedded directly in the Visual Editor, with complete freedom to customize both the website's backend and frontend.

The Visual Editor supports various interactions:

  • Click on a block in the editor to scroll to the corresponding element in the preview area, or click on an element in the preview to open the block in the editor
  • Orient yourself with outlines that appear around block elements in the preview area
  • Click elements in the preview area and use context menus to navigate between blocks
  • Make changes in the editor and view the updated content in real-time

These features deliver an intuitive experience for everyone on your team—from sales reps to HR managers, developers, and marketers.

Provide this seamless editing using the following model:

  1. Fetch draft content

    To render a preview in the editor, fetch the draft version of the content. Learn more about creating a preview deployment.

    import { storyblokInit, apiPlugin } from '@storyblok/js';
    const { storyblokApi } = storyblokInit({
    accessToken: 'YOUR_ACCESS_TOKEN',
    use: [apiPlugin],
    });
    // Fetch content
    const { data } = await storyblokApi.get("cdn/stories", {
    version: "draft",
    });

    The draft version of each story’s API response attaches a private _editable property to every block. This property enables live editing and instructs the Visual Editor how to handle the elements on the frontend:

    "body": [
    {
    "_uid": "2db95a85-e979-da7f-895b-c3a41fbc5f8d",
    "component": "page",
    "_editable": "\u003C!--#storyblok ... --\u003E"
    // Shortened for brevitiy
    }
    ]

    The _editable property contains a string of encoded JSON. When extracted and parsed, the JSON has four properties:

    • name: the block's name
    • space: the numeric space ID
    • uid: the block's unique ID
    • id: the numeric story ID

    Add these properties to your HTML so the Visual Editor can access them.

  2. Add HTML attributes

    Each block has two native data attributes to extract the values of the _editable property:

    • data-blok-c contains the stringified JSON
    • data-blok-uid contains the ID and UID in the pattern id-uid

    Storyblok's SDKs include utilities to format the data attributes:

    const editableOptions = storyblokEditable(blok);
    const element = `
    <div
    class="storyblok__outline"
    data-blok-c="${editableOptions["data-blok-c"]}"
    data-blok-uid="${editableOptions["data-blok-uid"]}"
    <!-- Content -->
    </div>`;

    Finally, connect the frontend to the Visual Editor using the StoryblokBridge.

  3. Enable the preview bridge

    The @storyblok/preview-bridge handles most of the heavy lifting required for live editing.

    Storyblok hosts the script on a dedicated CDN:

    Terminal window
    https://app.storyblok.com/f/storyblok-v2-latest.js

    Either load it manually via the URL above, or use one of Storyblok’s frontend SDKs, such as @storyblok/js:

    import { storyblokInit, apiPlugin } from '@storyblok/js';
    import { storyblokInit, apiPlugin, useStoryblokBridge } from '@storyblok/js';
    const { storyblokApi } = storyblokInit({
    accessToken: 'YOUR_ACCESS_TOKEN',
    use: [apiPlugin],
    });
    // Fetch content
    const { data } = await storyblokApi.get("cdn/stories", {
    version: "draft",
    });
    // Activate the StoryblokBridge
    useStoryblokBridge(data.story.id);

    Once activated, the bridge reloads the page each time the editor records a save or publish event.

    Customize and adjust the bridge's behavior with a manual implementation of the StoryblokBridge class. Learn more about the supported events and method in the package reference.

    The bridge is responsible for displaying context menus when the user clicks a block.

    • When save and publish events are configured, the menu display navigation actions.
    • When the input event is configured, the menu also displays editing actions.

    The website is now ready to communicate with Storyblok's Visual Editor.

The Visual Editor loads your webpage in an iframe. To view it, open SettingsVisual Editor and define a Preview URL.

The preview environment can be a dedicated deployment that builds from the same codebase as the production deployment, and loads preview functionality based on variables.

To learn more, follow the How to Create Preview & Production Environments and Deploy Your Website tutorial.

When the Visual Editor loads the page, it builds a URL from the base domain (example.com) and the story’s full slug (folder/example-uid).

It also appends several parameters to the URL:

  • _storyblok: the numeric story ID
  • _storyblok_tk[space_id]: the numeric space ID
  • _storyblok_tk[timestamp]: a UNIX timestamp
  • _storyblok_tk[token]: a validation token that combines _storyblok_tk[space_id]_storyblok_tk[timestamp], and the preview access token
  • _storyblok_release: the numeric release ID (requires the Releases App)
  • _storyblok_lang: the numeric language ID
  • _storyblok_c : the block's name (content type)

The result would look like this:

https://example.com/folder/example-uid?_storyblok=580906535&_storyblok_c=page&_storyblok_version=&_storyblok_lang=default&_storyblok_release=0&_storyblok_rl=1732540047643&_storyblok_tk[space_id]=313862&_storyblok_tk[timestamp]=1732540047&_storyblok_tk[token]=9d25c03de1478da57e37a166a7c053ce0aff9234

Depending on your local or server configurtion, the webpage embedded in the iframe might be blocked from the parent page (the Storyblok editor) or from the source page (your website).

If you encounter any problems when loading a webpage in the Visual Editor, add an SSL certificate or adjust the website's Content Security Policy (CSP).

Storyblok's security policy requires that you serve the preview over HTTPS (whether deployed or on localhost). To add an SSL certificate, use the tool that matches your web development framework:

The website's CSP might block clients from loading pages inside an <iframe>. To allow embedding, add Storyblok to the frame-ancestor directive:

Content-Security-Policy: frame-ancestors https://app.storyblok.com