Understanding the Visual Editor


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

The Visual Editor enables your editors to 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 bloks.

Screenshot of the visual editor

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

Visual Editor - Preview

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 domain we’re using to embed your site in the Space Setting under General > Location (default environment). 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, we will append the current full_slug of the Story you’re currently editing and add additional query params to your Preview URL. Those parameters allow you to identify when to load the draft content for the Visual Editor Preview if embedded.


Make sure to not have X-Frame-Options enabled for your iframe preview environment.


When using http for your Preview URLs the Storyblok UI will redirect to a non https version as well, otherwise Browser Security restrictions won’t allow us to embed your site. This is only recommended for local development domains such as http://localhost or similar. The API requests to the Management API will still be done using https.

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 JS Bridge not being initialized. Make sure to not have redirects or to pass the query parameters accordingly.

Visual Editor - Content

Visual Editor Content Area

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 JS 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 JS Bridge

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

<script src="//app.storyblok.com/f/storyblok-v2-latest.js" type="text/javascript">

Keep in mind that the Storyblok JS 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 V2.

Initializing the Storyblok JS 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  StoryblokBridge()
  storyblokInstance.on(['published', 'change'], () => {
    // reload page if save or publish is clicked

Storyblok Bridge V1 was automatically intialized when the preview token was present. This does not work with V2 anymore. The bridge needs to be initialized (new StoryblokBridge()) manually.

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 JS 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 JS 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 JS 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 Blok is currently selected.

In this process the Storyblok JS Bridge will also register the click EventListeners on your HTML elements that will open up the Story or Blok in the Visual Editor Content area.

Listen to the Visual Editor events

Events you can listen to in your own site using the Storyblok JS Bridge are events the Visual Editor sends through the Storyblok JS Bridge so you can react on content changes and UI actions. Some of the events sent to the Storyblok JS 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 input event contains the updated Story you can use to update your projects 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
customEvent after a custom event was fired in a custom field type