---
title: Migration to Vue 3
description: Discover Storyblok's documentation with comprehensive developer guides, user manuals, API references, and examples to help you get the most out of the headless CMS platform.
url: https://storyblok.com/docs/plugins/field-plugins/migration-to-vue-3
---

# Migration to Vue 3

In 2023, Storyblok launched a [new SDK](https://www.storyblok.com/docs/libraries/js/field-plugin-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

-   [Node.js](https://nodejs.org/) LTS
-   [Yarn](https://yarnpkg.com/)
-   [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) 5.2.0 or above
-   [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)

## Understanding the legacy field plugin

The simplistic version of legacy field plugin may look like this:

```javascript
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.

```bash
npx @storyblok/field-plugin-cli@latest create --template vue3
```

In the newly created project folder, open the `src/components/FieldPluginExample/Counter.vue` file.

```javascript
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

```javascript
<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.

```javascript
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:

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

## Actions

Due the introduction of the `useFieldPlugin` composable, you run actions differently.

### Set Content

```javascript
// before
this.$emit('changed-model', value);

// after
const plugin = useFieldPlugin();
plugin.actions.setContent(value);
```

### **Toggle Modal**

```javascript
// before
this.$emit('toggle-modal', booleanValue);
// after
const plugin = useFieldPlugin();
plugin.actions.setModalOpen(booleanValue);
```

### **Getting Current Context**

```javascript
// 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**

```javascript
// 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](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
-   **Removed** `initWith`: `initWith` 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](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](https://www.storyblok.com/docs/libraries/js/field-plugin-sdk).

## Pagination

-   [Previous: Examples](/docs/plugins/field-plugins/examples)
-   [Next: Introduction](/docs/plugins/tool-plugins)
