Almost EVERYONE who tried headless systems said they saw benefits. Download the state of CMS now!

Storyblok now on AWS Marketplace: Read more

O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

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.

Section titled Prerequisites 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.

Section titled Create a new Field Plugin 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
    

Section titled Understanding legacy Field Plugin 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').

Section titled Understanding new Field Plugin 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>
    

Section titled Getting data 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);
    

Section titled Actions 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);
    

Section titled Summary of changes 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

Section titled API Reference API Reference

To learn more about all the APIs, you can read the API Reference.