@storyblok/field-plugin
To bridge your field plugin application and the Visual Editor, Storyblok provides the @storyblok/field-plugin library. Storyblok also provides a command-line interface (CLI) for creating new field plugins from templates, so that you do not need to integrate @storyblok/field-plugin
yourself. It's easy to get started! See the Creating Field Plugin tutorial.
This article describes the API of @storyblok/field-plugin
.
Integrating @storyblok/field-plugin
To integrate @storyblok/field-plugin
into a frontend framework, bootstrap your application with the --template js
flag.
npx @storyblok/field-plugin-cli@latest --template js
The entry point of the application is in src/main.js
. Replace this code with the following:
const rootElement = document.createElement('div') rootElement.id = 'app' document.body.appendChild(rootElement)
This adds a new node to the body
element, to which you can mount your application.
Next, run createFieldPlugin()
as an effect. Your application is now listening to events from Storyblok's Visual Editor! As an argument, this function accepts a callback function that will be called whenever the state of the field plugin changes. This happens whenever the end-user edits the content, opens a modal, selects an asset, etc. A simple example with vanilla JavaScript may look like this:
import { createFieldPlugin } from '@storyblok/field-plugin' // This button is the only element in this application const buttonEl = document.createElement('button') document.body.appendChild(buttonEl) // Establish communication with the Visual Editor createFieldPlugin({ onUpdateState: (response) => { // Handle events from the Visual Editor by re-rendering the button element const content = typeof response.data.content === 'number' ? response.data.content : 0 buttonEl.textContent = `Increment: ${content}` buttonEl.onclick = () => response.actions.setContent(content+ 1) } })
createFieldPlugin()
returns a cleanup function that removes all created side effects. Call this function when your application unmounts.
API Reference
createFieldPlugin()
is a function that connects field plugins to the Storyblok Visual Editor. It accepts a callback
function that is invoked with a FieldPluginResponse
object whenever the state of the field plugin changes.
The function returns another function that can be used to clean up the event listeners that createFieldPlugin()
added.
Parameters:
callback
:(response: FieldPluginResponse) => void
: A callback function that is called whenever the state of the field plugin changes. The function is passed aFieldPluginResponse
object as its only argument.
Return Value: () => void
: A function that removes the event listeners added by createFieldPlugin()
.
FieldPluginResponse
FieldPluginResponse
is an object that represents the current state of a field plugin.
Properties:
Key | Defined when FieldPluginResponse.type is |
Description |
---|---|---|
type |
A string that indicates which overall state your field plugin is in. It can assume three values loading , error , and loaded . |
|
error |
"error" |
If FieldPluginResponse.type is error , the error property will contain an Error object instance. Otherwise, it is undefined . |
data |
"loaded" |
When FieldPluginResponse.type is loaded , this property will contain the state of the application that the Visual Editor has shared with the field plugin. Otherwise, it is undefined . |
actions |
"loaded" |
When FieldPluginResponse.type is loaded , this property will contain an object whose properties are functions that enables the field plugin application to interact with the Visual Editor. Otherwise, it is undefined . |
FieldPluginResponse.type
FieldPluginResponse.type
can assume three different values:
Value | Description |
---|---|
"loading" |
When the state is initially loaded and has not yet establish communication with the Visual Editor. |
"error" |
The plugin failed to load. This can happen, for example, if the field plugin URL is opened outside the Visual Editor. This is typically not a scenario that needs to be considered. |
"loaded" |
The plugin successfully loaded and is ready-to-use. |
FieldPluginResponse.data
FieldPluginResponse.data
has the following properties:
Key | Description |
---|---|
blockUid |
The UID of the block that the field plugin is part of. |
content |
The content of the field plugin that is part of the content. |
isModalOpen |
A boolean value that indicates whether the field plugin is embedded in a modal window. |
interfaceLang |
Specifies the language used in the Storyblok interface. |
options |
A dictionary/record of string key-value pairs, containing the options that were set up for this field plugin in the block schema. |
releaseId |
Represents the numerical identifier for the currently selected release. To utilize this functionality, ensure the Releases App is enabled. |
releases |
Contains an array of objects providing details about all releases associated with the current story. |
storyLang |
The language of the story. |
spaceId |
The ID of the space. |
story |
The story that was initially loaded. If the story is updated by the user, this value will remain unchanged. To update this after the initial load, call FieldPluginResponse.actions.requestContext() . |
storyId |
The ID of the story. |
token |
A draft access token to the Content Delivery API. |
translatable |
A boolean flag indicating whether the field plugin content is translatable. |
uid |
The UID of the field plugin. |
FieldPluginResponse.actions
FieldPluginResponse.actions
has the following properties:
Key | Description |
---|---|
setContent |
Updates the content of the field plugin. You can pass an object or a primitive value as an argument, and it will replace the old content. For example, setContent(3.14159) . |
setModalOpen |
Opens/Closes the modal window. For example, setModalOpen(true) . |
selectAsset |
Opens the asset selector. Returns a promise that gets resolved when the user selects an asset. For example, selectAsset().then((filename) => console.log(filename)) |
requestContext() |
Updates the request.data.story property to the version of the story that is currently opened in the Visual Editor. That is, the unsaved version of the story that exists in the user's browser memory. |
Manifest File for Field Plugins
The manifest file is a configuration that enhances the functionality of your field plugin. This JSON file, named field-plugin.config.json
and located in your project's root folder, is optional but highly recommended.
The manifest file allows you to configure options for your field plugin. When developing your field plugin with the Sandbox, the options are applied by default. Also, the deploy command automatically applies the options in production. So, you no longer need to configure the options manually.
Creating a Manifest File
If your field plugin doesn't already contain a manifest file, you can create one yourself at any time by following these steps:
- Begin by creating a
field-plugin.config.json
file in the root folder of your project. - Populate the file with the following content:
{
"options": []
}
That's all there is to it.
The options list within this file should consist of key-value objects representing the essential options required for your field plugin to function correctly, along with their corresponding values. This is an example of how it should be structured:
{
"options": [
{
"name": "myPluginInitialValue",
"value": 100
}
]
}
The option values will not be shared when this field plugin is added to a story. Only keys are configured for security reasons.
However, during development, you'll be able to access these values while using the Field Plugin Editor or Sandbox.
Now, you just need to access these options in your code like in the example below:
const plugin = useFieldPlugin()
console.log(plugin.data.options.myPluginInitialValue)
useFieldPlugin()
You can use useFieldPlugin()
for Vue 3 and React.
import { useFieldPlugin } from '@storyblok/field-plugin/vue3';
// or
import { useFieldPlugin } from '@storyblok/field-plugin/react';
const plugin = useFieldPlugin({ validateContent: ... });
console.log(plugin); // { type, data, actions }
// later
plugin.actions.setContent(...);
Using useFieldPlugin()
in multiple components
You can use useFieldPlugin()
in multiple components. However, in each case, the createFieldPlugin()
function will be executed, transitioning each plugin.type
from loading
to loaded
. Therefore, you must verify the plugin.type
in every component. Here is an example in React (The same applies to Vue):
function Parent() {
return (
<div>
<Child1 />
<Child2 />
</div>
);
}
function Child1() {
const plugin = useFieldPlugin();
return plugin.type === "loaded" && <div>Child1: {plugin.data}</div>;
}
function Child2() {
const plugin = useFieldPlugin();
return plugin.type === "loaded" && <div>Child2: {plugin.data}</div>;
}
This is fine. If you want to avoid checking if the plugin is loaded repeatedly, handle it in a parent component and pass the entire plugin
object to the children.
import type { FieldPluginResponse } from "@storyblok/field-plugin";
type Plugin = FieldPluginResponse<MyContentType>;
function Parent() {
const plugin = useFieldPlugin({ validateContent: undefined });
return (
plugin.type === "loaded" && (
<div>
<Child1 plugin={plugin} />
<Child2 plugin={plugin} />
</div>
)
);
}
function Child1({ plugin: Plugin }) {
return <div>Child1: {plugin.data}</div>;
}
function Child2({ plugin: Plugin }) {
return <div>Child2: {plugin.data}</div>;
}