Migrating to Vue 3
In 2023, Storyblok launched a new SDK for developing Field Plugins. While older Field Plugins only worked with Vue 2.5, the new SDK lets you develop with any frontend framework, including Vue 3 and React. The new SDK also has newer features, in-depth documentation, typescript support, and is being actively maintained. This tutorial guides you through the process of upgrading a legacy Field Plugin from Vue 2.5 to Vue 3, using the new SDK.
Prerequisites
This tutorial requires the following applications to be installed on your machine:
Also, please read the previous documents about Field Plugin to better understand this document.
Create a new Field Plugin
We no longer recommend creating a Field Plugin directly in the Field Plugin editor. Instead, we provide a new CLI that helps you create one. To do so, run the following command and select the "Vue 3" template.
npx @storyblok/field-plugin-cli@latest create --template vue3
Understanding legacy Field Plugin
The simplistic version of legacy Field Plugin may look like this:
const Fieldtype = {
mixins: [window.Storyblok.plugin],
template: `<div><input v-model="model.example" /></div>`,
methods: {
initWith() {
return {
plugin: 'test-field-plugin',
example: 'Hello World!'
}
},
pluginCreated() {
console.log('plugin:created')
}
},
watch: {
'model': {
handler: function (value) {
this.$emit('changed-model', value);
},
deep: true
}
}
}
Whenever model
changes, it emits an event changed-model
to its parent. And actually, there is a hidden parent component that is automatically injected by Storyblok. It receives this event, and finally send it to the Visual Editor via postMessage
.
Like above, in the legacy Field Plugins, all the communications happened with $emit('some-event')
.
Understanding new Field Plugin
In the previous "Create a new Field Plugin" section, we created a new Field Plugin. Inside the directory, open src/components/FieldPluginExample/Counter.vue
file. You will see code like this:
const {
data,
actions: { setContent },
} = useFieldPlugin()
// ...
setContent(value)
As you can see, this.$emit('changed-model', value)
is now plugin.actions.setContent(value)
.
The component above will become like the following:
<!-- src/components/Fieldtype.vue -->
<script setup>
import { useFieldPlugin } from '@storyblok/field-plugin/vue3'
const plugin = useFieldPlugin()
</script>
<template>
<div>
<input
:value="plugin.data.content"
@input="plugin.actions.setContent($event.target.value)"
/>
</div>
</template>
Getting data
In the legacy version, the mixin injected all the data into this
of the root component.
const Fieldtype = {
mixins: [window.Storyblok.plugin],
template: `<div><input v-model="model.example" /></div>`,
methods: {
initWith() {
return {
plugin: 'eltest-0707',
example: 'Hello World!'
}
},
pluginCreated() {
console.log('plugin:created')
}
},
watch: {
'model': {
handler: function (value) {
console.log('đź’ˇ data', {
blockId: this.blockId,
contentmodel: this.contentmodel,
model: this.model,
schema: this.schema,
spaceId: this.spaceId,
storyId: this.storyId,
storyItem: this.storyItem,
token: this.token,
uid: this.uid,
userId: this.userId,
});
this.$emit('changed-model', value);
},
deep: true
}
}
}
However, with the useFieldPlugin
, it's simpler.
const plugin = useFieldPlugin();
console.log(plugin.data);
Actions
Since the introduction of the useFieldPlugin
composition API, you run actions differently.
Set Content
// before
this.$emit('changed-model', value);
// after
const plugin = useFieldPlugin();
plugin.actions.setContent(value);
Toggle Modal
// before
this.$emit('toggle-modal', booleanValue);
// after
const plugin = useFieldPlugin();
plugin.actions.setModalOpen(booleanValue);
Getting Current Context
// before
this.$emit('get-context');
this.$onGetContext(() => {
console.log(this.storyItem);
})
// after
const plugin = useFieldPlugin();
plugin.actions.requestContext(); // this updates `plugin.data.story` asynchronously, which triggers a re-render.
Asset Selector
// before
// You had to include this proxy component:
<sb-asset-selector :uid="uid" field="your_model_attribute"></sb-asset-selector>
// after
const plugin = useFieldPlugin();
const asset = await plugin.actions.selectAsset();
console.log(asset.filename);
Summary of changes
Added content
: In the legacy version, you had to send an object containing plugin
property (e.g. { plugin: "my-plugin", some: "value" }
). Unlike before, now content
can be anything: string, boolean, number, object, array, etc. You can use anything as long as they can be serialized. You pass it from your Field Plugin to Storyblok's Visual Editor via postMessage
. To learn more about what you can send, you can read the structured code algorithm .
Removed initWith
: initWith
was a method called by the mixin window.Storyblok.plugin
. Now that we're using useFieldPlugin
composition API, we no longer need initWith
method.
Also, now an empty string is given to your Field Plugin at plugin.data.content
by default.
Removed pluginCreated
: You can use the regular lifecycle callbacks like onMounted()
.
Removed default style: A default css file used to be injected (https://plugins.storyblok.com/assets/css/index-latest.css ), but it's not there anymore and you have full control over styling.
Removed api
: The storyblok-js-client
is no longer included by default. You can include it by running npm install storyblok-js-client
.
Removed $sb
API Reference
To learn more about all the APIs, you can read the API Reference .