Migration 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 actively maintained. This tutorial guides you through the process of upgrading a legacy field plugin from Vue 2.5 to Vue 3.
Prerequisites
Section titled “Prerequisites”Understanding the legacy field plugin
Section titled “Understanding the 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 a changed-model event to its parent. There is a hidden parent component that is automatically injected by Storyblok. It receives this event and forwards it to the Visual Editor via postMessage. All communication occurred using $emit('some-event').
Understanding the new field plugin
Section titled “Understanding the 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 vue3In the newly created project folder, open the src/components/FieldPluginExample/Counter.vue file.
const { data, actions: { setContent },} = useFieldPlugin()
// ...
setContent(value)Most importantly, this.$emit('changed-model', value) is now plugin.actions.setContent(value).
The Vue 2 component above will become like the following:
<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
Section titled “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 much simpler:
const plugin = useFieldPlugin();console.log(plugin.data);Actions
Section titled “Actions”Due the introduction of the useFieldPlugin composable, you run actions differently.
Set Content
Section titled “Set Content”// beforethis.$emit('changed-model', value);
// afterconst plugin = useFieldPlugin();plugin.actions.setContent(value);Toggle Modal
Section titled “Toggle Modal”// beforethis.$emit('toggle-modal', booleanValue);// afterconst plugin = useFieldPlugin();plugin.actions.setModalOpen(booleanValue);Getting Current Context
Section titled “Getting Current Context”// beforethis.$emit('get-context');this.$onGetContext(() => { console.log(this.storyItem);})
// afterconst plugin = useFieldPlugin();plugin.actions.requestContext(); // this updates `plugin.data.story` asynchronously, which triggers a re-render.Asset Selector
Section titled “Asset Selector”// before// You had to include this proxy component:<sb-asset-selector :uid="uid" field="your_model_attribute"></sb-asset-selector>
// afterconst plugin = useFieldPlugin();const asset = await plugin.actions.selectAsset();console.log(asset.filename);Summary of changes
Section titled “Summary of changes”- Added
content: In the legacy version, you had to send an object containingpluginproperty (e.g.{ plugin: "my-plugin", some: "value" }). Unlike before, nowcontentcan be anything: string, boolean, number, object, array, etc. You can use anything as long as it can be serialized. Pass content from a field plugin to Storyblok's Visual Editor viapostMessage. To learn more about what you can send, read the structured code algorithm. - Removed
initWith:initWithwas a method called by the mixinwindow.Storyblok.plugin. Thanks to theuseFieldPlugincomposable, it is no longer required. - An empty string is given to your field plugin at
plugin.data.contentby default. - Removed
pluginCreated: You can use the regular lifecycle callbacks such asonMounted(). - Removed default style: A default css file used to be injected (https://plugins.storyblok.com/assets/css/index-latest.css). With the new version, you have full control over the styling.
- Removed
api: Thestoryblok-js-clientis no longer included by default. You can include it by runningnpm install storyblok-js-client. - Removed
$sb
API Reference
Section titled “API Reference”To learn more about all the APIs, you can read the API Reference.
Get in touch with the Storyblok community