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.