Storyblok
Search Storyblok's Documentation
  1. Introduction

Introduction

Tool plugins extend Storyblok’s Visual Editor by adding custom tool windows as standalone applications embedded in iframes. They communicate with Storyblok via cross-window messaging and the Management API.

Content editors open tools directly from the Visual Editor. For example, the Autosave plugin, allowing editors to access tool functionality related to the story being edited. Common use cases for tools include:

  • Analyzing content
  • Transforming content
  • Performing actions on content

Examples in the app directory include tools such as Export and Import Translatable Fields and Autosave.

Tool plugins function similarly to space plugins but have some key differences. Tool plugins embed directly in the Visual Editor and can access the story being edited, while space plugins cannot.

Integrating with the Visual Editor

Your plugin is currently a standalone app embedded in Storyblok. To connect it to Storyblok’s Visual Editor, use window.postMessage.

Setting the Height

Tools appear stacked vertically in the Visual Editor, with iframes set at a specific height in pixels. When the height of your tool changes (e.g., on initial load), notify the Visual Editor to adjust the iframe height using:

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

In this example, height is set to 500 px. The tool value must match the plugin's slug in settings. Use minimal vertical space to avoid blocking tools below, as stacking order can’t be controlled.

Automatic Height Adjustment

To auto-adjust height, register a ResizeObserver that updates the iframe height when document height changes. Set html element’s height to auto:

html {
  height: auto;
  overflow: hidden;
}

In your component:

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);

Disconnect the observer when the component unmounts:cpd

observer.disconnect();

Reading the Story

To read the story in the Visual Editor, set up an event listener:

const handleMessage = (e) => {
  console.log(e.data);
};

window.addEventListener('message', handleMessage, false);

Remove the listener on component unmount:

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

Request context by sending a message:

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

The Visual Editor will respond with the story context, including story details and language, in this format:

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

Integrating with the Management API

Server-side code can interact with the Management API by reading accessToken and spaceId from http.IncomingMessage:

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

The @storyblok/app-extension-auth library automatically appends storyId and spaceId as query parameters, enabling access to accessToken and region from the session store. With these values, send Management API requests such as:

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

Take a look at our starter projects for similar examples.

To update the current story, use the Management API. Note that changes won’t appear immediately in the Visual Editor – prompt the user to manually refresh the window to see updates.