Storyblok
Search Storyblok's Documentation
  1. Migration to Vue 3

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

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

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

In 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:

<!-- 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 much simpler:

const plugin = useFieldPlugin();console.log(plugin.data);

Actions

Due the introduction of the useFieldPlugin composable, 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 it can be serialized. Pass content from a field plugin to Storyblok's Visual Editor via postMessage. To learn more about what you can send, read the structured code algorithm.
  • Removed initWithinitWith was a method called by the mixin window.Storyblok.plugin. Thanks to the useFieldPlugin composable, it is no longer required.
  • An empty string is given to your field plugin at plugin.data.content by default.
  • Removed pluginCreated: You can use the regular lifecycle callbacks such as onMounted().
  • 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: 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.