O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

The Storyblok Bridge

To build a bridge between Storyblok and your website, you need to load our storyblok-v2-latest script on your page. This script will communicate via iframe with Storyblok to tell the editing interface which component needs to be opened when the user clicks on it.


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

This is done using a simple HTML comment before that element. The data for this comment is shipped in the draft JSON by adding the key _editable to each component.

Include the script inside your <body> tag

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

Since the script has a considerable size, in most cases, you would want to load the script only when you're inside Storyblok edit mode or on a preview deployment. You can check the asynchronous loading example in the FAQ below.

Some methods to check for the edit mode:

  • Check for the URL parameter _storyblok  (see FAQ below)
  • Check for a cookie (You could set a cookie the first time the URL has _storyblok in it)

Usage of StoryblokBridge

Once your bridge is loaded, you can initialize it with new StoryblokBridge(config).

      const { StoryblokBridge, location } = window
const storyblokInstance = new StoryblokBridge()

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


StoryblokBridge configuration

  • resolveRelations (array of strings): If you use the resolve_relations parameter in your API requests you need to tell the Storyblok bridge to resolve the relations too. Example: ["article.author", "article.category"]
      const storyblokInstance = new StoryblokBridge({
  resolveRelations: ["article.author"]
  • resolveLinks (string: "url" | "link" | "story"): If you use the resolve_links parameter in your API requests you need to tell the Storyblok bridge to resolve the links.
      const storyblokInstance = new StoryblokBridge({
  resolveLinks: "url"
  • customParent (string): If you are using the Storyblok editor on your own domain a URL needs to be provided. Default is: https://app.storyblok.com.
      const storyblokInstance = new StoryblokBridge({
  customParent: 'http://localhost:8080',
  • preventClicks (boolean): If you want to prevent the iframe events, like clicking on a link, to happen, you can set this option. Default is: false.
      const storyblokInstance = new StoryblokBridge({
  preventClicks: true,

Please checkout also our detailed guide about the Visual Editor and the Storyblok Bridge.


You can listen to events calling the on handler:

      storyblokInstance.on('event_name', (event) => {

Published and Change event

For the published event this would look like:

      storyblokInstance.on('published', (event) => {
  // invalidate cache, ...

You get a 404 error after changing the slug of a Story?
If you use Storyblok with server-side rendering and want to reload whenever the content changed you need to make an additional check on the slugChanged attribute. This makes sure that the page doesn’t throw a 404 error if you change the slug:

      storyblokInstance.on(['published', 'change'], (event) => {
  if (!event.slugChanged) {

We can use multiple events together by providing the event.name  inside of an array. The example above will reload the page, if the user clicks save or publish.

event payload: published, change

  "reload": true,
  "action": "change", // or published
  "slug": "home",
  "storyId": 24840769,
  "slugChanged": true

Input event

The input event is triggered after a user changes the value of a field. It's used to enable live updates of the content.

      storyblokInstance.on('input', (event) => {
    // Access currently changed but not yet saved content via: 

event payload: input

  "action": "input",
  "story":  { ... } // complete story

enterEditmode event

The enterEditmode event is triggered after the editor has been initialized. It can be used to request the draft content, when we're inside of Storyblok.

      storyblokInstance.on('enterEditmode', (event) => {
        // loading the draft version on initial enter of editor
          .get(`cdn/stories/${event.storyId}`, {
            version: 'draft',
          .then(({ data }) => {
          .catch((error) => {

event payload: enterEditmode

  "reload": true,
  "action": "enterEditmode",
  "storyId": "24840769",
  "componentNames": {
    "feature": "Feature",
    "grid": "Grid",
    "page": "Page",
    "teaser": "Teaser",

See the complete list of events below:

EventWill be emitted
inputafter a user changes the value of a field
changeafter the user saves the content
publishedafter the user clicks publish
unpublishedafter the user clicks unpublish
enterEditmodeafter the editor has been initialized in the editmode
customEventcustom event used by third party plugins

Check for editor environment

With storyblokInstance.isInEditor() and storyblokInstance.pingEditor() you can check if your user has opened a page inside Storyblok. If that is the case, you can use the storyblok-js-client to request the draft version of the content.

      // Call pingEditor to see if the user is in the editor
storyblokInstance.pingEditor(() => {
  if (storyblokInstance.isInEditor()) {
       //  load the draft version
  } else {
        // load the published version

Component Menus

Once you listen to events on the Visual Editor, you will be able to click the components and see their name with an outline {1}. If you listen only the save or publish events, you will see a simple menu, that allows navigation to parent components {2}. If you listen to the input event, you will see a more extended menu {3}, that allows you to have components actions like adding blocks before or after, duplicating a component, or moving the component forwards or backward. You can also delete the components directly.

Menu for the Bridge

Parameters Storyblok appends to your URL

Storyblok appends a few URL parameters when opening your application inside the Visual Editor iframe:

_storyblokthe content entries ID
_storyblok_tk[space_id]the space ID
_storyblok_tk[timestamp]a timestamp
_storyblok_tk[token]a validation token. Combination of space_id, timestamp and your preview_token
_storyblok_releasecurrently selected id of a release
_storyblok_langcurrently selected language
_storyblok_ccontent type of the story


Loading the Bridge asynchronously

One way of loading the bridge asynchronously is by adding this function and calling the function at any point where you want to load the bridge. This function will check if the bridge is already present and then once it's loaded, call a callback function.

      function loadBridge(callback) {
    const existingScript = document.getElementById("storyblokBridge");
    if (!existingScript) {
      const script = document.createElement("script");
      script.src = "//app.storyblok.com/f/storyblok-v2-latest.js";
      script.id = "storyblokBridge";
      script.onload = () => {
    } else {


The initialization of the bridge should happen after the storyblok-v2-latest.js the script was loaded, so for example on the callback function of the loadBridge function above.

      loadBridge(() => {
    const { StoryblokBridge, location } = window
    const storyblokInstance = new StoryblokBridge()

Checking if you are inside of Storyblok

Since your application will be loaded inside an iframe in Storyblok, the iframe will be passed a _storyblok parameter that we can check for. If this parameter is present, we should be inside the Visual Editor.

      if(window.location.search.includes('_storyblok') {
  // load the bridge only inside of Storyblok

Why is the live preview not working?

Check if you are not redirecting to another URL by clicking "Open Draft" in the top right submenu. The URL should contain the query parameter _storyblok.

How to validate if the user is viewing your site in the Storyblok editor?

If the user opens your page in Storyblok, we add a few parameters which you can use to securely validate their use of the edit mode.

You will need that validation to load the right version of your content to the right users. The draft version is for the editor and the published version is for the public.

Checking for _storyblok

A simple validation would be to check if there is the _storyblok parameter in URL. This could be done in the frontend or in the backend. But for a secure check, we recommend implementing the logic in the backend and validate the _storyblok_tk parameter.

Here are some examples of how to securely check if the user is in edit mode:

import crypto from 'crypto'
// getQueryParam = access requests URL param by name

let validationString = getQueryParam('_storyblok_tk[space_id]') + ':' + YOUR_PREVIEW_TOKEN + ':' + getQueryParam('_storyblok_tk[timestamp]')
let validationToken = crypto.createHash('sha1').update(validationString).digest('hex')
if (getQueryParam('_storyblok_tk[token]') == validationToken && 
    getQueryParam('_storyblok_tk[timestamp]') > Math.floor(Date.now()/1000)-3600) { 
    // you're in the edit mode.
    this.editMode = true 

$sb = $app['request']->query->get('_storyblok_tk');
if (!empty($sb)) {
    $pre_token = $sb['space_id'] . ':' . YOUR_PREVIEW_TOKEN . ':' . $sb['timestamp'];
    $token = sha1($pre_token);
    if ($token == $sb['token'] && (int)$sb['timestamp'] > strtotime('now') - 3600) {
        $app['config.storyblok.edit_mode'] = true;
        // you're in the edit mode.

In the demo above we expect 1 hour as the time of a token being valid. So basically that an editor will work max 1 hour on one page before switching to another entry inside the editor or refreshing the browser window. You can extend that by adjusting 3600 with the value you need.