Storyblok Raises $80M Series C - Read News

Skip to main content

Tool Plugins

INFO:

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.

Tool plugins allow you to extend Storyblok's Visual Editor by adding new tool windows with custom functionality. These are standalone applications that are embedded within iframes, which communicate with Storyblok by means of cross-window communication, and via the Management API.

Content editors opens tools from the Visual Editor. In this screen capture, the Autosave plugin is installed on the space.

Tools can contain any functionality, but they are most useful when they do things that relate to the story that is being edited in the same context. Some example areas of application are to:

  • Analyze content
  • Transform content
  • Perform actions on the content

Some concrete examples are the following tools, which are to be found in the app directory:

Tool plugins are in principle very similar to custom sidebar applications. The difference is where in the application they are embedded, and what frontend features they have access to. For example, tools are embedded in the Visual Editor and can read the story that is currently being edited; while custom sidebar applications do not have such capability.

Section titled Create a Tool Plugin

To create a tool plugin, follow the tutorial on how to create a new plugin. When asked which type, select Tool.

When you have completed the entire tutorial and gotten the starter to run, enter the Visual Editor, and click open the tools tab to see the result.

The first time the tool is opened, an admin will need to approve it.

You may need to navigate back to the Visual Editor again, where you should see the plugin:

Section titled Integrate with the Visual Editor

At this point, the plugin that you have just created is just a standalone application that happens to be embedded within Storyblok. Next, you will connect it to the Storyblok frontend application by using window.postMessage.

Section titled Setting the Height

All tools that are installed on a space appears in a vertical list–they are stacked on top of each other. The iframe windows that embed the tools have a height that is set in pixels. However, a tool will change its height during its lifecycle; for example, on the initial load. When that happens, the tool needs to notify the Visual Editor that the iframe element's height should be updated. Do this by sending a heightChanged message to the Visual Editor:

window.parent.postMessage({
  action: 'tool-changed',
  tool: 'my-tool-plugin-name',
  event: 'heightChange',
  height: 500
}, '*')

In the example above, the height is set to 500px. the tool property must be set to the name of the tool that is defined in the plugin settings.

Since tools are stacked on top of each other, a tool that takes up a lot of vertical space might obfuscate tools that appear underneath it. It's not possible to decide which order tools appear in. Therefore, it is recommended to not use too much vertical room for tools.

Is this the only tool that is available? It's hard to tell, since the tool occupies the entire height.

Section titled Automatic Height

To automatically set the height of the tool plugin, register a ResizeObserver that updates the iframe element's height whenever the tool plugin's document height is mutated:

First, ensure that the html element's height is not set to 100vh:

        
      html {
  height: auto;
  overflow: hidden;
}
    

In one of your components, register the ResizeObserver:

        
      const handleResize = () => {
  const height = document.body.clientHeight
  window.parent.postMessage({
        action: 'tool-changed',
        tool: 'storyblok-gmbh@jl-dev-tool',
        event: 'heightChange',
        height,
      }, "*")
    }
  const observer = new ResizeObserver(handleResize)
    observer.observe(document.body)
    

Remember to clean up the side-effect whenever the component unmounts:

        
      observer.disconnect()
    

Section titled Reading the Story

Tools can read the story object that is being edited in the Visual Editor.

In one of the components, set up an event listener:

        
      const handleMessage =  (e) => {
  console.log(e.data)
}
    window.addEventListener('message',handleMessage, false)
    

Don't forget to also clean up the side effect (when the component unmounts):

        
      window.removeEventListener('message',handleMessage, false)
    

After the event listener has been registered, send a message to request the context:

        
      window.parent.postMessage({
  action: 'tool-changed',
  tool: 'my-plugin-name',
  event: 'getContext'
}, "*")
    

The Visual Editor will reply by sending the context in a message to the plugin application. This message includes the story and the language of the story. Following the example above, you will see a message of the following structure in the console:

{
  "action": "get-context",
  "story": {
    "name": "hello444",
    "uuid": "45bbd2c4-b418-4737-8f4f-d893ec1f7f10",
    "content": {
      ...
    },
    ...
  },
  "language": ""
}

Section titled Integrate with the Management API

Server-side code is able to send requests to the Management API. To do this, read the accessToken and spaceId from the http.IncomingMessage:

        
      const { query } = req
const sessionStore = sessionCookieStore(authHandlerParams)({ req, res })
const { accessToken, region, spaceId } = await sessionStore.get(query)
    

Note that @storyblok/app-extension-auth, which is included in all starters, appends the storyId and spaceId as query parameters whenever a page is loaded (in req.query). These are used to read the accessToken and region from the session store.

Using these values, you can perform requests with the Management API; for example:

        
      new StoryblokClient({
    oauthToken: `Bearer ${accessToken}`,
    region,
})
.get(`spaces/${spaceId.toString(10)}/stories`, {
    sort_by: 'updated_at:desc',
})
    

All starter projects include a similar example.

You can use the Management API to update the current story. However, this change will not automatically be reflected in the Visual Editor, so you will need to instruct the user to manually refresh the window.