Skip to content Skip to footer navigation
You are reading the Statamic 6 Alpha docs. 👀

Upgrade from Vue 2 to Vue 3

A guide for upgrading Vue 2 to 3.

Overview

As part of the Statamic 6 release, Vue was upgraded to version 3.

Vite

package.json:

{
"scripts": {
- "dev": "vite",
+ "dev": "vite build --watch",
"build": "vite build"
},
"devDependencies": {
- "@vitejs/plugin-vue2": "^6.0.1",
+ "@statamic/cms": "file:./vendor/statamic/cms/resources/js/package"
}
}

vite.config.js:

-import vue from '@vitejs/plugin-vue2';
import laravel from 'laravel-vite-plugin';
+import statamic from '@statamic/cms/vite-plugin';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
+ statamic(),
laravel({
refresh: true,
input: ['resources/js/cp.js']
}),
],
});

Laravel Mix

If you are still using Laravel Mix, you will need to switch to Vite.

Composition API

Converting your components to the Composition API is not required.

However, since it is now supported, you might love it. We recommend it. It could greatly clean up your components. Check out this example component written using the Options API:

<script>
import { FooComponent, BarComponent } from './components';
import { Button, Card } from '@statamic/cms/ui';
export default {
components: {
FooComponent,
BarComponent,
Button,
Card
},
data() {
return {
firstName: 'John',
lastName: 'Smith',
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
},
methods: {
changeName(first, last) {
this.firstName = first;
this.lastName = last;
}
},
watch: {
fullName(newName) {
console.log(`Name changed to ${newName}`);
}
}
}
</script>

And now converted to the Composition API:

<script setup>
import { FooComponent, BarComponent } from './components';
import { Button, Card } from '@statamic/cms/ui';
import { ref, computed, watch } from 'vue';
const firstName = ref('John');
const lastName = ref('Smith');
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
function changeName(first, last) {
firstName.value = first;
lastName.value = last;
}
watch(fullName, (newName) => console.log(`Name changed to ${newName}`));
</script>

Imports

You should be importing components and other files from @statamic/cms. You may have been previously importing explicit files straight from the vendor directory. This is not supported.

-import Something from '../../../vendor/statamic/cms/resources/js/Something.vue';
+import { Something } from '@statamic/cms';

Fieldtypes

Mixins

Mixins will now need to be explicitly imported. Assuming you've updated vite.config.js explained above, you should be able to add the following to your fieldtype:

+import { FieldtypeMixin as Fieldtype } from '@statamic/cms';
export default {
mixins: [Fieldtype],
data() {
return {
//
}
}
}

Events

If you are manually emitting an input event from within a fieldtype, you should change it to update:value.

-this.$emit('input', foo);
+this.$emit('update:value', foo);
Hot Tip!

You should be using this.update() if possible anyway.

-this.$emit('input', foo);
+this.update(foo);
A troll pointing a teaching stick

Props, events, and v-model

Vue 3 changes how v-model works.

Fieldtypes

To avoid needing to change all references to the value prop, we've kept the prop as-is. If you are using v-model directly on a fieldtype component, you will need to specify :value now.

Note that this is only if you are using a fieldtype component from within another component.

<my-fieldtype
v-model="foo" <!-- [tl! --] -->
v-model:value="foo" <!-- [tl! ++] -->
/>

Components that no longer support v-model

If you were using v-model, you must change to the appropriate prop and event:

<my-component
v-model="foo" <!--[tl! --]-->
:value="foo" <!--[tl! ++]-->
@input="foo = $event" <!--[tl! ++]-->
/>
Component Prop Event
<slugify> to @slugified
<publish-container> values @updated

Components that support v-model

If you were not using v-model and instead using the value prop and input event, you will need to change to model-value and @update:model-value.

<component
:value="foo" <!--[tl! --]-->
@input="foo = $event" <!--[tl! --]-->
:model-value="foo" <!--[tl! ++]-->
@update:model-value="foo = $event" <!--[tl! ++]-->
/>
Component Notes
<form-group>
<v-select> This is from the vue-select package.

Slots

The slot syntax changed. If you were previously using slot-scope, you should change to v-slot:

Default slot:

-<component>
- <div slot-scope="{ ... }"></div>
-</component>
+<component v-slot="{ ... }">
+ <div></div>
+</component>

Named slots:

<component>
- <template slot="something" slot-scope="{ ... }"></template>
+ <template v-slot:something="{ ... }"></template>
</component>

You can also use the shorthand:

-<template v-slot:something="{ ... }">
+<template #something="{ ... }">

Bard

Since the $on, $off, and $once methods have been removed from Vue 3, Bard events need to work differently. They have been moved into an event bus on the bard component.

You might be using these methods if you have a custom Bard toolbar button (via this.bard) or Prosemirror mark/node element (via vm).

-bard.$on(...);
-bard.$off(...);
-bard.$once(...);
+bard.events.on(...);
+bard.events.off(...);
+bard.events.once(...);

Components

Components should be registered using the $components API rather than directly through Statamic:

-Statamic.component('my-fieldtype', MyFieldtype);
+Statamic.$components.register('my-fieldtype', MyFieldtype);

Vuex to Pinia

Publish Components

The primary reason for Statamic including Vuex at all was for the Publish components. These no longer use Vuex (or Pinia). If you were using $store in your code it's probably for those.

+import { publishContextKey } from '@statamic/cms/ui';
export default {
- inject: [
- 'storeName',
- ],
+ inject: {
+ publishContext: { from: publishContextKey },
+ },
methods: {
getValue(handle) {
- return this.$store.state.publish[this.storeName].values[handle];
+ return this.publishContext.values.value[handle]; // values is a ref() so you need .value to unwrap it.
},
setValue(handle, newValue) {
- this.$store.dispatch(`publish/${this.storeName}/setFieldValue`), newValue);
+ this.publishContext.setFieldValue(newValue);
}
}
}

The most common place for the store to be accessed like this is in a fieldtype, so they automatically get the publish context injected as injectedPublishContainer and have all the refs unwrapped as publishContainer.

export default {
mixins: [Fieldtype],
- inject: [
- 'storeName',
- ],
methods: {
getValue(handle) {
- return this.$store.state.publish[this.storeName].values[handle];
+ return this.publishContainer.values[handle];
},
setValue(handle, newValue) {
- this.$store.dispatch(`publish/${this.storeName}/setFieldValue`), newValue);
+ this.publishContainer.setFieldValue(newValue);
}
}
}

Custom Stores

If you were adding your own Vuex stores, you should switch to Pinia. Rather than registering to a global Vuex store, you define your own store and use it directly in your components.

Use the provided $pinia helper rather than trying to import from your own version of Pinia.

-// In bootstrapping...
-Statamic.$store.registerModule(['publish', 'myStore'], {
- state: { foo: 'bar' },
- mutations: {
- doSomething(payload) {
- //
- }
- },
- actions: {
- doSomething(context, payload) {
- context.commit('doSomething', payload);
- }
- }
-});
-
-// In component...
-const foo = this.$store.state.publish.myStore.foo;
-this.$store.dispatch('publish/myStore/doSomething', 'example');
+// mystore.js...
+const useMyStore = Statamic.$pinia.defineStore('myStore', {
+ state: { foo: 'bar' },
+ actions: {
+ doSomething() {
+ //
+ }
+ }
+});
+
+// In component...
+import { useMyStore } from './mystore';
+const store = useMyStore();
+const foo = store.foo;
+store.doSomething('example');

Bard Tiptap API

Previously you would be able to access Tiptap components directly through the Bard API. They will now be provided to you when using the various callbacks. For example:

-const { Node, Mark, Extension } = Statamic.$bard.tiptap.core;
-
-Statamic.$bard.addExtension(() => {
- return [
- Node.create({...}),
- Mark.create({...}),
- Extension.create({...}),
- ]
-})
+Statamic.$bard.addExtension(({ tiptap }) => {
+ const { Node, Mark, Extension } = tiptap.core;
+
+ return [
+ Node.create({...}),
+ Mark.create({...}),
+ Extension.create({...}),
+ ]
+})

If you were importing/exporting the component, you should change to a "factory" function that accepts the Tiptap API and returns the component. For example:

-import TextColor from './TextColor.js';
-Statamic.$bard.addExtension(() => TextColor);
-
-// TextColor.js
-const { Mark } = Statamic.$bard.tiptap.core;
-export default Mark.create({});
+import TextColor from './TextColor.js';
+Statamic.$bard.addExtension(({ tiptap }) => TextColor(tiptap));
+
+// TextColor.js
+export default function (tiptap) {
+ const { Mark } = tiptap.core;
+ return Mark.create({});
+}

Component Substitutions

With the introduction of the UI component library, a number of components have been replaced by more modern versions.

Publish

If you were building a custom publish form using publish-* components, they have been replaced by newer equivalents through the UI component library.

You would have previously needed to set up "prop- and event-ception". Now the PublishContainer becomes the source of truth and you can define the child components as needed (or not!).

Before:

<publish-container
:blueprint="blueprint"
:values="values"
:meta="meta"
:errors="errors"
@updated="values = $event"
v-slot="{ setFieldValue, setFieldMeta }"
>
<publish-tabs
@updated="setFieldValue"
@meta-updated="setFieldMeta"
/>
</publish-container>

After:

<script>
import { PublishContainer } from '@statamic/cms/ui';
</script>
<template>
<PublishContainer
:blueprint="blueprint"
:meta="meta"
:errors="errors"
v-model="values"
/>
</template>

View the Publish component docs page for more details.

Listing

If you were building custom listings using the data-list components, they have been replaced by newer equivalents.

Before, you probably grabbed our listing mixin, added the necessary properties to make it work, and added a bunch of components to your template, each of them with a bunch of props.

<script>
import Listing from '../../../../vendor/statamic/cms/resources/js/components/Listing.vue';
export default {
mixins: [Listing],
data() {
return {
requestUrl: '/cp/my-listing-url',
}
}
}
</script>
<template>
<data-list
:rows="items"
:columns="columns"
:sort="false"
:sort-column="sortColumn"
:sort-direction="sortDirection"
v-slot="{ hasSelections }"
>
<data-list-filter-presets ... />
<data-list-search ... />
<data-list-filters ... />
<data-list-bulk-actions ... />
<data-list-table>
<template slot="cell-title" slot-scope="{ row }">...</template>
<template slot="cell-another" slot-scope="{ row }">...</template>
<template slot="actions" slot-scope="{ row }">...</template>
</data-list-table>
<data-list-pagination ... />
</data-list>
</template>

After, you can use the new Listing component, pass in a URL, some props, and not worry about any nested components. The layout will figure itself out.

<script>
import { Listing } from '@statamic/cms/ui';
export default {
data() {
return {
requestUrl: '/cp/my-listing-url',
}
}
}
</script>
<template>
<Listing
:url="requestUrl"
:columns="columns"
:filters="filters"
:action-url="actionUrl"
>
<template #cell-title="{ row }">...</template>
<template #cell-another="{ row }">...</template>
<template #prepended-row-actions="{ row }"></template>
</Listing>
</template>

View the Listing component docs page for more details.

Dropdown List

The dropdown-list component has been replaced by the Dropdown UI component.

<dropdown-list>
<template v-slot:trigger>
<button>Click me</button>
</template>
<dropdown-item redirect="/somewhere">Text</dropdown-item>
</dropdown-list>
<script>
import { Dropdown, DropdownMenu, DropdownItem } from '@statamic/cms';
</script>
<template>
<Dropdown>
<template #trigger>
<button>Click me</button>
</template>
<DropdownMenu>
<DropdownItem href="/somewhere" text="Text" />
</DropdownMenu>
</Dropdown>
</template>

You can also forgo the imports and just use ui-dropdown, ui-dropdown-menu, and ui-dropdown-item respectively.

Inputs

Input components such as <text-input>, <textarea-input>, <select-input>, and <toggle-input> have been removed in favor of Input, Textarea, Combobox, Switch UI components respectively.

<script>
+import { Input, Textarea, Combobox, Switch } from '@statamic/cms/ui';
</script>
<template>
- <text-input v-model="textValue" />
- <textarea-input v-model="textValue" />
- <select-input v-model="textValue" />
- <toggle-input v-model="textValue" />
+ <Input v-model="textValue" />
+ <Textarea v-model="textareaValue" />
+ <Combobox v-model="comboboxValue" />
+ <Switch v-model="switchValue" />
</template>

Vue Devtools

Vue Devtools is no longer automatically enabled during local development. To use it, you'll need to use our opt-in dev build.

Your app must have debug mode enabled:

APP_DEBUG=true

The dev build must be either published or symlinked:

# Published
php artisan vendor:publish --tag=statamic-cp-dev
# Symlinked
ln -s /path/to/vendor/statamic/cms/resources/dist-dev public/vendor/statamic/cp-dev

You shouldn't commit this to your repo or use this in production.

Removals

A number of items have been removed. If you feel they shouldn't have been removed, please contact us and we can evaluate bringing them back.

  • We had a String.includes() polyfill that has been removed since it's widely supported now.
  • Underscore.js mixins objMap, objFilter, and objReject have been removed.
  • resource_url and file_icon methods are no longer available in Vue templates but are still available as global functions.
  • The deprecated $slugify function has been removed in favor of the $slug API.
  • The v-focus directive has been removed.
  • The store/storeName are no longer included field action payloads.
  • The vue-select package has been removed. If you're using <v-select> you should change to the Combobox UI component.