Storyblok Raises $80M Series C - Read News

Skip to main content

Understanding the Visual Editor


On May 13th, 2024, Storyblok started gradually rolling out a new design for its Visual Editor. Therefore, the Visual Editor product screenshots depicted in this resource may not match what you encounter in the Storyblok App. For more information and a detailed reference, please consult this FAQ on the new Visual Editor design.


This chapter assumes you’ve read Content Structures and understand how to access your content.

The Visual Editor enables your users to create and edit their content with an in-context preview, including the possibility to click on components in their website. This feature is optional and does not depend on any specific technology. It will not alter the structure of your HTML or rearrange the DOM. The visual editor will not affect your production sites as it can be enabled only when inside the Storyblok UI. The visual editor will not allow inline editing; changing content will always be done in the Storyblok UI and the normal form inputs and custom fields you have defined for your content types and blocks.

  • {1} Visual Editor - Preview: Your project embedded in an iFrame.
  • {2} Visual Editor - Content: Bloks and Fields on the right-hand side.
Screenshot of Storyblok's Visual Editor

Visual Editor - Preview

Screenshot of the Visual Editor Preview

The left-hand side of the Visual Editor contains an iframe of your actual website, which we call Preview. This is not something we will render on our end, it is in fact your own implementation hosted on your infrastructure completely decoupled from Storyblok. You can configure the location used to embed your site in the Space Settings under the Visual Editor tab. You can configure multiple Preview URLs, which enables you to add development and deployment environments as an option for the Preview source.

While your project is embedded in the Visual Editor, the full_slug of the story you’re currently editing, and other additional query params, will be appended to the Preview URL. Those parameters allow you to identify when to load the draft content for the Visual Editor Preview if embedded.


If you have problems loading the website preview you might need to add ALLOW-FROM to your X-Frame-Options header. You can do this in your server side code or in your web server configuration settings.


If you have problems loading the website preview in the Visual Editor with an error related to the Content Security Policy directive, you have to add as frame-ancestor in the Content Security Policy directive.

Preview URL structure
          └── Preview URL      │                          │
                               └── full_slug              └── additional Query Params

Additional Query Params

Query Param Description
_storyblok the numeric story ID
_storyblok_tk[space_id] the space ID
_storyblok_tk[timestamp] a unix timestamp
_storyblok_tk[token] a validation token. Combination of _storyblok_tk[space_id], _storyblok_tk[timestamp] and your Preview Token - Usage
_storyblok_release id of the active release
_storyblok_lang currently selected language
_storyblok_c content type of the story

Redirects can remove the URL parameters above and result in the Storyblok Bridge not being initialized. Make sure you don't have redirects and that you pass the query parameters accordingly.

Visual Editor - Content

Screenshot of the Visual Editor Content menu

In this area of the Visual Editor you and your editors are able to adjust and change your content by using the Fields and Bloks you’ve defined for your content type.

Bridge between the Visual Editor Preview and Content

We’ve created a toolkit that enables you to set-up a bridge between the Visual Editor Content and your project embedded in the Preview. We refer to this toolkit as Storyblok JS Bridge. The Storyblok JS Bridge enables you to listen to events such as change, published, unpublished, input, and enterEditmode. You can allow click events on your own HTML elements which will then open up the correct Story and Blok in the Visual Editor Content area.

The Storyblok Bridge is used for two main tasks:

  1. Enabling click events on your HTML elements to open up in the Visual Editor Content area.
  2. Listen to Visual Editor events to update your content or trigger processes.

Installing the Storyblok Bridge

Installation of the Storyblok Bridge is done by including a script tag on your site or using one of our modules for specific technologies.

<script src="//" type="text/javascript">

Keep in mind that the Storyblok Bridge increases the payload of your site and shouldn't be included in the production builds of your site.


Please check out also our comprehensive guide about the Storyblok Bridge.

Initializing the Storyblok Bridge

Once the script for the bridge is included you need to initialize it. In this example, we also add the functionality of reloading the content of the iFrame for every publish and change event.

// Initialize the Storyblok JS Bridge
const { StoryblokBridge, location } = window
const storyblokInstance = new 


  storyblokInstance.on(['published', 'change'], () => {
    // reload page if save or publish is clicked

Please note that the _editable property is only included in the draft version of the story.

Enabling click events on your HTML Elements

To enable the click events that will send messages to the Visual Editor you will have to output the content of the _editable property before each of your own HTML Elements. The content of the _editable property contains a HTML comment for each Story and Blok. The Storyblok Bridge will then automatically register click events of the HTML Element that follow that HTML comment.

Example Explanation

When you load one story using the URL parameter version=draft from the Content Delivery API you will receive a response like this:

  "story": {
    "id": 172223,
    "content": {
      "_editable": "<!--#storyblok#{\"name\": \"page\", \"space\": \"14141\", \"uid\": \"b370f32d-cd89-4dd3-9437-16b5e9746b31\", \"id\": \"172223\"}-->",
      "component": "page",
      "body": [
          "_editable": "<!--#storyblok#{\"name\": \"hero\", \"space\": \"14141\", \"uid\": \"98b685a1-da10-4df2-837e-3429d5ea6f88\", \"id\": \"172223\"}-->",
          "headline": "This is my headline!"

Read more about the different versions of your Content in Accessing your Content.

We can then push that data through any template engine or framework of your choice to render HTML.

<div class="content-type">
    {% for blok in story.content.body %}
        <div class="blok">
    {% endfor %}

You can use any templating engine you want. The example above is using Mustache syntax.

We end up with HTML that looks like this:

<!--#storyblok#{\"name\": \"page\", \"space\": \"14141\", \"uid\": \"b370f32d-cd89-4dd3-9437-16b5e9746b31\", \"id\": \"172223\"}-->
<div class="content-type">
    <!--#storyblok#{\"name\": \"hero\", \"space\": \"14141\", \"uid\": \"98b685a1-da10-4df2-837e-3429d5ea6f88\", \"id\": \"172223\"}-->
    <div class="blok">
        This is my headline!

The result of combining the template and the loaded draft content is the above HTML, which will be loaded by the browser. At this point, the Storyblok Bridge will be loaded and initialized. Your part of the integration is already done by adding the HTML comments and from here on out the Storyblok JS Bridge will take over.

During this initialization, the Storyblok Bridge looks for every HTML comment in your HTML. Each HTML comment starting with <!--#storyblok# will be parsed. The parsed JSON will then be used to add two HTML attributes data-blok-c and data-blok-uid and the class storyblok``--``outline to the HTML element right after the HTML comment.

<!--#storyblok#{\"name\": \"page\", \"space\": \"14141\", \"uid\": \"b370f32d-cd89-4dd3-9437-16b5e9746b31\", \"id\": \"172223\"}-->
<div class="content-type storyblok--outline" data-blok-c="{escapedJSONData}" data-blok-uid="b370f32d-cd89-4dd3-9437-16b5e9746b31">
    <!--#storyblok#{\"name\": \"hero\", \"space\": \"14141\", \"uid\": \"98b685a1-da10-4df2-837e-3429d5ea6f88\", \"id\": \"172223\"}-->
    <div class="blok storyblok--outline" data-blok-c="{escapedJSONData}" data-blok-uid="98b685a1-da10-4df2-837e-3429d5ea6f88">
        This is my headline!

The class storyblok--outline will add a dotted border to your HTML element while hovering on your site in the Visual Editor Preview. Once you click on one HTML element the name of the blok will be added with a █ #09b3af colored border to show which block is currently selected.

In this process, the Storyblok Bridge will also register the click eventListeners on your HTML elements that will open up the story or block in the Visual Editor Content area.

Listen to the Visual Editor events

Events you can listen to on your own site, using the Storyblok Bridge, are events the Visual Editor sends through the bridge so you can react to content changes and UI actions. Some of the events sent to the Storyblok Bridge include a payload for you to use in your project.

// listen to a single event
storyblokInstance.on('event_name', (payload) => {

// listen to multiple events
storyblokInstances.on(['event_name_1', 'event_name_2'], (payload) => {

Example Event Usage: Change and Published

To get a new version of the content every time the Save or Publish button is pressed, listen to the change and published events and trigger a reload. You can also update your state manually by loading the content from the Content Delivery API.

// Listens on multiple events and does a basic website refresh
storyblokInstance.on(['change', 'published'], () => {

Example Event Usage: Input

To update the content in your project on input changes in the Visual Editor Content area, even before the content is saved, you can use the input event. The payload passed in the event contains the updated story, so you can use it to update your project content state in real-time.

Visual Editor Event Types

Event Name Will be emitted
input after a user changes the value of a field
change after the user saves the content
published after the user clicks publish
unpublished after the user clicks unpublish
enterEditmode after the editor has been initialized in the editmode