Close
Upgrade to Vue 3 | Vue 2 EOL

Migration from Vue 1.x

FAQ

Woah - this is a super long page! Does that mean 2.0 is completely different, I’ll have to learn the basics all over again, and migrating will be practically impossible?

I’m glad you asked! The answer is no. About 90% of the API is the same and the core concepts haven’t changed. It’s long because we like to offer very detailed explanations and include a lot of examples. Rest assured, this is not something you have to read from top to bottom!

Where should I start in a migration?

  1. Start by running the migration helper on a current project. We’ve carefully minified and compressed a senior Vue dev into a simple command line interface. Whenever they recognize an obsolete feature, they’ll let you know, offer suggestions, and provide links to more info.

  2. After that, browse through the table of contents for this page in the sidebar. If you see a topic you may be affected by, but the migration helper didn’t catch, check it out.

  3. If you have any tests, run them and see what still fails. If you don’t have tests, just open the app in your browser and keep an eye out for warnings or errors as you navigate around.

  4. By now, your app should be fully migrated. If you’re still hungry for more though, you can read the rest of this page - or dive in to the new and improved guide from the beginning. Many parts will be skimmable, since you’re already familiar with the core concepts.

How long will it take to migrate a Vue 1.x app to 2.0?

It depends on a few factors:

If I upgrade to Vue 2, will I also have to upgrade Vuex and Vue Router?

Only Vue Router 2 is compatible with Vue 2, so yes, you’ll have to follow the migration path for Vue Router as well. Fortunately, most applications don’t have a lot of router code, so this likely won’t take more than an hour.

As for Vuex, even version 0.8 is compatible with Vue 2, so you’re not forced to upgrade. The only reason you may want to upgrade immediately is to take advantage of the new features in Vuex 2, such as modules and reduced boilerplate.

Templates

Fragment Instances removed

Every component must have exactly one root element. Fragment instances are no longer allowed. If you have a template like this:

<p>foo</p>
<p>bar</p>

It’s recommended to wrap the entire contents in a new element, like this:

<div>
<p>foo</p>
<p>bar</p>
</div>

Upgrade Path

Run your end-to-end test suite or app after upgrading and look for console warnings about multiple root elements in a template.

Lifecycle Hooks

beforeCompile removed

Use the created hook instead.

Upgrade Path

Run the migration helper on your codebase to find all examples of this hook.

compiled replaced

Use the new mounted hook instead.

Upgrade Path

Run the migration helper on your codebase to find all examples of this hook.

attached removed

Use a custom in-DOM check in other hooks. For example, to replace:

attached: function () {
doSomething()
}

You could use:

mounted: function () {
this.$nextTick(function () {
doSomething()
})
}

Upgrade Path

Run the migration helper on your codebase to find all examples of this hook.

detached removed

Use a custom in-DOM check in other hooks. For example, to replace:

detached: function () {
doSomething()
}

You could use:

destroyed: function () {
this.$nextTick(function () {
doSomething()
})
}

Upgrade Path

Run the migration helper on your codebase to find all examples of this hook.

init renamed

Use the new beforeCreate hook instead, which is essentially the same thing. It was renamed for consistency with other lifecycle methods.

Upgrade Path

Run the migration helper on your codebase to find all examples of this hook.

ready replaced

Use the new mounted hook instead. It should be noted though that with mounted, there’s no guarantee to be in-document. For that, also include Vue.nextTick/vm.$nextTick. For example:

mounted: function () {
this.$nextTick(function () {
// code that assumes this.$el is in-document
})
}

Upgrade Path

Run the migration helper on your codebase to find all examples of this hook.

v-for

v-for Argument Order for Arrays changed

When including an index, the argument order for arrays used to be (index, value). It is now (value, index) to be more consistent with JavaScript’s native array methods such as forEach and map.

Upgrade Path

Run the migration helper on your codebase to find examples of the obsolete argument order. Note that if you name your index arguments something unusual like position or num, the helper will not flag them.

v-for Argument Order for Objects changed

When including a property name/key, the argument order for objects used to be (name, value). It is now (value, name) to be more consistent with common object iterators such as lodash’s.

Upgrade Path

Run the migration helper on your codebase to find examples of the obsolete argument order. Note that if you name your key arguments something like name or property, the helper will not flag them.

$index and $key removed

The implicitly assigned $index and $key variables have been removed in favor of explicitly defining them in v-for. This makes the code easier to read for developers less experienced with Vue and also results in much clearer behavior when dealing with nested loops.

Upgrade Path

Run the migration helper on your codebase to find examples of these removed variables. If you miss any, you should also see console errors such as: Uncaught ReferenceError: $index is not defined

track-by replaced

track-by has been replaced with key, which works like any other attribute: without the v-bind: or : prefix, it is treated as a literal string. In most cases, you’d want to use a dynamic binding which expects a full expression instead of a key. For example, in place of:

<div v-for="item in items" track-by="id">

You would now write:

<div v-for="item in items" v-bind:key="item.id">

Upgrade Path

Run the migration helper on your codebase to find examples of track-by.

v-for Range Values changed

Previously, v-for="number in 10" would have number starting at 0 and ending at 9. Now it starts at 1 and ends at 10.

Upgrade Path

Search your codebase for the regex /\w+ in \d+/. Wherever it appears in a v-for, check to see if you may be affected.

Props

coerce Prop Option removed

If you want to coerce a prop, setup a local computed value based on it instead. For example, instead of:

props: {
username: {
type: String,
coerce: function (value) {
return value
.toLowerCase()
.replace(/\s+/, '-')
}
}
}

You could write:

props: {
username: String,
},
computed: {
normalizedUsername: function () {
return this.username
.toLowerCase()
.replace(/\s+/, '-')
}
}

There are a few advantages:

Upgrade Path

Run the migration helper on your codebase to find examples of the coerce option.

twoWay Prop Option removed

Props are now always one-way down. To produce side effects in the parent scope, a component needs to explicitly emit an event instead of relying on implicit binding. For more information, see:

Upgrade Path

Run the migration helper on your codebase to find examples of the twoWay option.

.once and .sync Modifiers on v-bind removed

Props are now always one-way down. To produce side effects in the parent scope, a component needs to explicitly emit an event instead of relying on implicit binding. For more information, see:

Upgrade Path

Run the migration helper on your codebase to find examples of the .once and .sync modifiers.

Prop Mutation deprecated

Mutating a prop locally is now considered an anti-pattern, e.g. declaring a prop and then setting this.myProp = 'someOtherValue' in the component. Due to the new rendering mechanism, whenever the parent component re-renders, the child component’s local changes will be overwritten.

Most use cases of mutating a prop can be replaced by one of these options:

Upgrade Path

Run your end-to-end test suite or app after upgrading and look for console warnings about prop mutations.

Props on a Root Instance replaced

On root Vue instances (i.e. instances created with new Vue({ ... })), you must use propsData instead of props.

Upgrade Path

Run your end-to-end test suite, if you have one. The failed tests should alert to you to the fact that props passed to root instances are no longer working.

Computed properties

cache: false deprecated

Caching invalidation of computed properties will be removed in future major versions of Vue. Replace any uncached computed properties with methods, which will have the same result.

For example:

template: '<p>message: {{ timeMessage }}</p>',
computed: {
timeMessage: {
cache: false,
get: function () {
return Date.now() + this.message
}
}
}

Or with component methods:

template: '<p>message: {{ getTimeMessage() }}</p>',
methods: {
getTimeMessage: function () {
return Date.now() + this.message
}
}

Upgrade Path

Run the migration helper on your codebase to find examples of the cache: false option.

Built-In Directives

Truthiness/Falsiness with v-bind changed

When used with v-bind, the only falsy values are now: null, undefined, and false. This means 0 and empty strings will render as truthy. So for example, v-bind:draggable="''" will render as draggable="true".

For enumerated attributes, in addition to the falsy values above, the string "false" will also render as attr="false".

Note that for other directives (e.g. v-if and v-show), JavaScript’s normal truthiness still applies.

Upgrade Path

Run your end-to-end test suite, if you have one. The failed tests should alert to you to any parts of your app that may be affected by this change.

Listening for Native Events on Components with v-on changed

When used on a component, v-on now only listens to custom events $emitted by that component. To listen for a native DOM event on the root element, you can use the .native modifier. For example:

<my-component v-on:click.native="doSomething"></my-component>

Upgrade Path

Run your end-to-end test suite, if you have one. The failed tests should alert to you to any parts of your app that may be affected by this change.

debounce Param Attribute for v-model removed

Debouncing is used to limit how often we execute Ajax requests and other expensive operations. Vue’s debounce attribute parameter for v-model made this easy for very simple cases, but it actually debounced state updates rather than the expensive operations themselves. It’s a subtle difference, but it comes with limitations as an application grows.

These limitations become apparent when designing a search indicator, like this one for example:

{{ searchIndicator }}

Using the debounce attribute, there’d be no way to detect the “Typing” state, because we lose access to the input’s real-time state. By decoupling the debounce function from Vue however, we’re able to debounce only the operation we want to limit, removing the limits on features we can develop:

<!--
By using the debounce function from lodash or another dedicated
utility library, we know the specific debounce implementation we
use will be best-in-class - and we can use it ANYWHERE. Not only
in our template.
-->
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.js"></script>
<div id="debounce-search-demo">
<input v-model="searchQuery" placeholder="Type something">
<strong>{{ searchIndicator }}</strong>
</div>
new Vue({
el: '#debounce-search-demo',
data: {
searchQuery: '',
searchQueryIsDirty: false,
isCalculating: false
},
computed: {
searchIndicator: function () {
if (this.isCalculating) {
return '⟳ Fetching new results'
} else if (this.searchQueryIsDirty) {
return '... Typing'
} else {
return '✓ Done'
}
}
},
watch: {
searchQuery: function () {
this.searchQueryIsDirty = true
this.expensiveOperation()
}
},
methods: {
// This is where the debounce actually belongs.
expensiveOperation: _.debounce(function () {
this.isCalculating = true
setTimeout(function () {
this.isCalculating = false
this.searchQueryIsDirty = false
}.bind(this), 1000)
}, 500)
}
})

Another advantage of this approach is there will be times when debouncing isn’t quite the right wrapper function. For example, when hitting an API for search suggestions, waiting to offer suggestions until after the user has stopped typing for a period of time isn’t an ideal experience. What you probably want instead is a throttling function. Now since you’re already using a utility library like lodash, refactoring to use its throttle function instead takes only a few seconds.

Upgrade Path

Run the migration helper on your codebase to find examples of the debounce attribute.

lazy or number Param Attributes for v-model replaced

The lazy and number param attributes are now modifiers, to make it more clear what That means instead of:

<input v-model="name" lazy>
<input v-model="age" type="number" number>

You would use:

<input v-model.lazy="name">
<input v-model.number="age" type="number">

Upgrade Path

Run the migration helper on your codebase to find examples of the these param attributes.

value Attribute with v-model removed

v-model no longer cares about the initial value of an inline value attribute. For predictability, it will instead always treat the Vue instance data as the source of truth.

That means this element:

<input v-model="text" value="foo">

backed by this data:

data: {
text: 'bar'
}

will render with a value of “bar” instead of “foo”. The same goes for a <textarea> with existing content. Instead of:

<textarea v-model="text">
hello world
</textarea>

You should ensure your initial value for text is “hello world”.

Upgrade Path

Run your end-to-end test suite or app after upgrading and look for console warnings about inline value attributes with v-model.

v-model with v-for Iterated Primitive Values removed

Cases like this no longer work:

<input v-for="str in strings" v-model="str">

The reason is this is the equivalent JavaScript that the <input> would compile to:

strings.map(function (str) {
return createElement('input', ...)
})

As you can see, v-model‘s two-way binding doesn’t make sense here. Setting str to another value in the iterator function will do nothing because it’s only a local variable in the function scope.

Instead, you should use an array of objects so that v-model can update the field on the object. For example:

<input v-for="obj in objects" v-model="obj.str">

Upgrade Path

Run your test suite, if you have one. The failed tests should alert to you to any parts of your app that may be affected by this change.

v-bind:style with Object Syntax and !important removed

This will no longer work:

<p v-bind:style="{ color: myColor + ' !important' }">hello</p>

If you really need to override another !important, you must use the string syntax:

<p v-bind:style="'color: ' + myColor + ' !important'">hello</p>

Upgrade Path

Run the migration helper on your codebase to find examples of style bindings with !important in objects.

v-el and v-ref replaced

For simplicity, v-el and v-ref have been merged into the ref attribute, accessible on a component instance via $refs. That means v-el:my-element would become ref="myElement" and v-ref:my-component would become ref="myComponent". When used on a normal element, the ref will be the DOM element, and when used on a component, the ref will be the component instance.

Since v-ref is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for. For example:

<p v-for="item in items" v-bind:ref="'item' + item.id"></p>

Previously, v-el/v-ref combined with v-for would produce an array of elements/components, because there was no way to give each item a unique name. You can still achieve this behavior by giving each item the same ref:

<p v-for="item in items" ref="items"></p>

Unlike in 1.x, these $refs are not reactive, because they’re registered/updated during the render process itself. Making them reactive would require duplicate renders for every change.

On the other hand, $refs are designed primarily for programmatic access in JavaScript - it is not recommended to rely on them in templates, because that would mean referring to state that does not belong to the instance itself. This would violate Vue’s data-driven view model.

Upgrade Path

Run the migration helper on your codebase to find examples of v-el and v-ref.

v-else with v-show removed

v-else no longer works with v-show. Use v-if with a negation expression instead. For example, instead of:

<p v-if="foo">Foo</p>
<p v-else v-show="bar">Not foo, but bar</p>

You can use:

<p v-if="foo">Foo</p>
<p v-if="!foo && bar">Not foo, but bar</p>

Upgrade Path

Run the migration helper on your codebase to find examples of the v-else with v-show.

Custom Directives simplified

Directives have a greatly reduced scope of responsibility: they are now only used for applying low-level direct DOM manipulations. In most cases, you should prefer using components as the main code-reuse abstraction.

Some of the most notable differences include:

Fortunately, since the new directives are much simpler, you can master them more easily. Read the new Custom Directives guide to learn more.

Upgrade Path

Run the migration helper on your codebase to find examples of defined directives. The helper will flag all of them, as it's likely in most cases that you'll want to refactor to a component.

Directive .literal Modifier removed

The .literal modifier has been removed, as the same can be easily achieved by providing a string literal as the value.

For example, you can update:

<p v-my-directive.literal="foo bar baz"></p>

to:

<p v-my-directive="'foo bar baz'"></p>

Upgrade Path

Run the migration helper on your codebase to find examples of the `.literal` modifier on a directive.

Transitions

transition Attribute replaced

Vue’s transition system has changed quite drastically and now uses <transition> and <transition-group> wrapper elements, rather than the transition attribute. It’s recommended to read the new Transitions guide to learn more.

Upgrade Path

Run the migration helper on your codebase to find examples of the transition attribute.

Vue.transition for Reusable Transitions replaced

With the new transition system, you can now use components for reusable transitions.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.transition.

Transition stagger Attribute removed

If you need to stagger list transitions, you can control timing by setting and accessing a data-index (or similar attribute) on an element. See an example here.

Upgrade Path

Run the migration helper on your codebase to find examples of the transition attribute. During your update, you can transition (pun very much intended) to the new staggering strategy as well.

Events

events option removed

The events option has been removed. Event handlers should now be registered in the created hook instead. Check out the $dispatch and $broadcast migration guide for a detailed example.

Vue.directive('on').keyCodes replaced

The new, more concise way to configure keyCodes is through Vue.config.keyCodes. For example:

// enable v-on:keyup.f1
Vue.config.keyCodes.f1 = 112

Upgrade Path

Run the migration helper on your codebase to find examples of the the old keyCode configuration syntax.

$dispatch and $broadcast replaced

$dispatch and $broadcast have been removed in favor of more explicitly cross-component communication and more maintainable state management solutions, such as Vuex.

The problem is event flows that depend on a component’s tree structure can be hard to reason about and are very brittle when the tree becomes large. They don’t scale well and only set you up for pain later. $dispatch and $broadcast also do not solve communication between sibling components.

One of the most common uses for these methods is to communicate between a parent and its direct children. In these cases, you can actually listen to an $emit from a child with v-on. This allows you to keep the convenience of events with added explicitness.

However, when communicating between distant descendants/ancestors, $emit won’t help you. Instead, the simplest possible upgrade would be to use a centralized event hub. This has the added benefit of allowing you to communicate between components no matter where they are in the component tree - even between siblings! Because Vue instances implement an event emitter interface, you can actually use an empty Vue instance for this purpose.

For example, let’s say we have a todo app structured like this:

Todos
├─ NewTodoInput
└─ Todo
└─ DeleteTodoButton

We could manage communication between components with this single event hub:

// This is the event hub we'll use in every
// component to communicate between them.
var eventHub = new Vue()

Then in our components, we can use $emit, $on, $off to emit events, listen for events, and clean up event listeners, respectively:

// NewTodoInput
// ...
methods: {
addTodo: function () {
eventHub.$emit('add-todo', { text: this.newTodoText })
this.newTodoText = ''
}
}
// DeleteTodoButton
// ...
methods: {
deleteTodo: function (id) {
eventHub.$emit('delete-todo', id)
}
}
// Todos
// ...
created: function () {
eventHub.$on('add-todo', this.addTodo)
eventHub.$on('delete-todo', this.deleteTodo)
},
// It's good to clean up event listeners before
// a component is destroyed.
beforeDestroy: function () {
eventHub.$off('add-todo', this.addTodo)
eventHub.$off('delete-todo', this.deleteTodo)
},
methods: {
addTodo: function (newTodo) {
this.todos.push(newTodo)
},
deleteTodo: function (todoId) {
this.todos = this.todos.filter(function (todo) {
return todo.id !== todoId
})
}
}

This pattern can serve as a replacement for $dispatch and $broadcast in simple scenarios, but for more complex cases, it’s recommended to use a dedicated state management layer such as Vuex.

Upgrade Path

Run the migration helper on your codebase to find examples of $dispatch and $broadcast.

Filters

Filters Outside Text Interpolations removed

Filters can now only be used inside text interpolations ({{ }} tags). In the past we’ve found using filters within directives such as v-model, v-on, etc led to more complexity than convenience. For list filtering on v-for, it’s also better to move that logic into JavaScript as computed properties, so that it can be reused throughout your component.

In general, whenever something can be achieved in plain JavaScript, we want to avoid introducing a special syntax like filters to take care of the same concern. Here’s how you can replace Vue’s built-in directive filters:

Replacing the debounce Filter

Instead of:

<input v-on:keyup="doStuff | debounce 500">
methods: {
doStuff: function () {
// ...
}
}

Use lodash’s debounce (or possibly throttle) to directly limit calling the expensive method. You can achieve the same as above like this:

<input v-on:keyup="doStuff">
methods: {
doStuff: _.debounce(function () {
// ...
}, 500)
}

For more on the advantages of this strategy, see the example here with v-model.

Replacing the limitBy Filter

Instead of:

<p v-for="item in items | limitBy 10">{{ item }}</p>

Use JavaScript’s built-in .slice method in a computed property:

<p v-for="item in filteredItems">{{ item }}</p>
computed: {
filteredItems: function () {
return this.items.slice(0, 10)
}
}

Replacing the filterBy Filter

Instead of:

<p v-for="user in users | filterBy searchQuery in 'name'">{{ user.name }}</p>

Use JavaScript’s built-in .filter method in a computed property:

<p v-for="user in filteredUsers">{{ user.name }}</p>
computed: {
filteredUsers: function () {
var self = this
return self.users.filter(function (user) {
return user.name.indexOf(self.searchQuery) !== -1
})
}
}

JavaScript’s native .filter can also manage much more complex filtering operations, because you have access to the full power of JavaScript within computed properties. For example, if you wanted to find all active users and case-insensitively match against both their name and email:

var self = this
self.users.filter(function (user) {
var searchRegex = new RegExp(self.searchQuery, 'i')
return user.isActive && (
searchRegex.test(user.name) ||
searchRegex.test(user.email)
)
})

Replacing the orderBy Filter

Instead of:

<p v-for="user in users | orderBy 'name'">{{ user.name }}</p>

Use lodash’s orderBy (or possibly sortBy) in a computed property:

<p v-for="user in orderedUsers">{{ user.name }}</p>
computed: {
orderedUsers: function () {
return _.orderBy(this.users, 'name')
}
}

You can even order by multiple columns:

_.orderBy(this.users, ['name', 'last_login'], ['asc', 'desc'])

Upgrade Path

Run the migration helper on your codebase to find examples of filters being used inside directives. If you miss any, you should also see console errors.

Filter Argument Syntax changed

Filters’ syntax for arguments now better aligns with JavaScript function invocation. So instead of taking space-delimited arguments:

<p>{{ date | formatDate 'YY-MM-DD' timeZone }}</p>

We surround the arguments with parentheses and delimit the arguments with commas:

<p>{{ date | formatDate('YY-MM-DD', timeZone) }}</p>

Upgrade Path

Run the migration helper on your codebase to find examples of the old filter syntax. If you miss any, you should also see console errors.

Built-In Text Filters removed

Although filters within text interpolations are still allowed, all of the filters have been removed. Instead, it’s recommended to use more specialized libraries for solving problems in each domain (e.g. date-fns to format dates and accounting for currencies).

For each of Vue’s built-in text filters, we go through how you can replace them below. The example code could exist in custom helper functions, methods, or computed properties.

Replacing the json Filter

You actually don’t need to for debugging anymore, as Vue will nicely format output for you automatically, whether it’s a string, number, array, or plain object. If you want the exact same functionality as JavaScript’s JSON.stringify though, then you can use that in a method or computed property.

Replacing the capitalize Filter

text[0].toUpperCase() + text.slice(1)

Replacing the uppercase Filter

text.toUpperCase()

Replacing the lowercase Filter

text.toLowerCase()

Replacing the pluralize Filter

The pluralize package on NPM serves this purpose nicely, but if you only want to pluralize a specific word or want to have special output for cases like 0, then you can also easily define your own pluralize functions. For example:

function pluralizeKnife (count) {
if (count === 0) {
return 'no knives'
} else if (count === 1) {
return '1 knife'
} else {
return count + 'knives'
}
}

Replacing the currency Filter

For a very naive implementation, you could do something like this:

'$' + price.toFixed(2)

In many cases though, you’ll still run into strange behavior (e.g. 0.035.toFixed(2) rounds up to 0.04, but 0.045 rounds down to 0.04). To work around these issues, you can use the accounting library to more reliably format currencies.

Upgrade Path

Run the migration helper on your codebase to find examples of the obsolete text filters. If you miss any, you should also see console errors.

Two-Way Filters replaced

Some users have enjoyed using two-way filters with v-model to create interesting inputs with very little code. While seemingly simple however, two-way filters can also hide a great deal of complexity - and even encourage poor UX by delaying state updates. Instead, components wrapping an input are recommended as a more explicit and feature-rich way of creating custom inputs.

As an example, we’ll now walk the migration of a two-way currency filter:

It mostly works well, but the delayed state updates can cause strange behavior. For example, try entering 9.999 into one of those inputs. When the input loses focus, its value will update to $10.00. When looking at the calculated total however, you’ll see that 9.999 is what’s stored in our data. The version of reality that the user sees is out of sync!

To start transitioning towards a more robust solution using Vue 2.0, let’s first wrap this filter in a new <currency-input> component:

This allows us add behavior that a filter alone couldn’t encapsulate, such as selecting the content of an input on focus. Now the next step will be to extract the business logic from the filter. Below, we pull everything out into an external currencyValidator object:

This increased modularity not only makes it easier to migrate to Vue 2, but also allows currency parsing and formatting to be:

Having this validator extracted out, we’ve also more comfortably built it up into a more robust solution. The state quirks have been eliminated and it’s actually impossible for users to enter anything wrong, similar to what the browser’s native number input tries to do.

We’re still limited however, by filters and by Vue 1.0 in general, so let’s complete the upgrade to Vue 2.0:

You may notice that:

Upgrade Path

Run the migration helper on your codebase to find examples of filters used in directives like v-model. If you miss any, you should also see console errors.

Slots

Duplicate Slots removed

It is no longer supported to have <slot>s with the same name in the same template. When a slot is rendered it is “used up” and cannot be rendered elsewhere in the same render tree. If you must render the same content in multiple places, pass that content as a prop.

Upgrade Path

Run your end-to-end test suite or app after upgrading and look for console warnings about duplicate slots v-model.

slot Attribute Styling removed

Content inserted via named <slot> no longer preserves the slot attribute. Use a wrapper element to style them, or for advanced use cases, modify the inserted content programmatically using render functions.

Upgrade Path

Run the migration helper on your codebase to find CSS selectors targeting named slots (e.g. [slot="my-slot-name"]).

Special Attributes

keep-alive Attribute replaced

keep-alive is no longer a special attribute, but rather a wrapper component, similar to <transition>. For example:

<keep-alive>
<component v-bind:is="view"></component>
</keep-alive>

This makes it possible to use <keep-alive> on multiple conditional children:

<keep-alive>
<todo-list v-if="todos.length > 0"></todo-list>
<no-todos-gif v-else></no-todos-gif>
</keep-alive>

When <keep-alive> has multiple children, they should eventually evaluate to a single child. Any child other than the first one will be ignored.

When used together with <transition>, make sure to nest it inside:

<transition>
<keep-alive>
<component v-bind:is="view"></component>
</keep-alive>
</transition>

Upgrade Path

Run the migration helper on your codebase to find keep-alive attributes.

Interpolation

Interpolation within Attributes removed

Interpolation within attributes is no longer valid. For example:

<button class="btn btn-{{ size }}"></button>

Should either be updated to use an inline expression:

<button v-bind:class="'btn btn-' + size"></button>

Or a data/computed property:

<button v-bind:class="buttonClasses"></button>
computed: {
buttonClasses: function () {
return 'btn btn-' + size
}
}

Upgrade Path

Run the migration helper on your codebase to find examples of interpolation used within attributes.

HTML Interpolation removed

HTML interpolations ({{{ foo }}}) have been removed in favor of the v-html directive.

Upgrade Path

Run the migration helper on your codebase to find HTML interpolations.

One-Time Bindings replaced

One time bindings ({{* foo }}) have been replaced by the new v-once directive.

Upgrade Path

Run the migration helper on your codebase to find one-time bindings.

Reactivity

vm.$watch changed

Watchers created via vm.$watch are now fired before the associated component rerenders. This gives you the chance to further update state before the component rerender, thus avoiding unnecessary updates. For example, you can watch a component prop and update the component’s own data when the prop changes.

If you were previously relying on vm.$watch to do something with the DOM after a component updates, you can instead do so in the updated lifecycle hook.

Upgrade Path

Run your end-to-end test suite, if you have one. The failed tests should alert to you to the fact that a watcher was relying on the old behavior.

vm.$set changed

vm.$set is now an alias for Vue.set.

Upgrade Path

Run the migration helper on your codebase to find examples of the obsolete usage.

vm.$delete changed

vm.$delete is now an alias for Vue.delete.

Upgrade Path

Run the migration helper on your codebase to find examples of the obsolete usage.

Array.prototype.$set removed

Use Vue.set instead.

Upgrade Path

Run the migration helper on your codebase to find examples of .$set on an array. If you miss any, you should see console errors from the missing method.

Array.prototype.$remove removed

Use Array.prototype.splice instead. For example:

methods: {
removeTodo: function (todo) {
var index = this.todos.indexOf(todo)
this.todos.splice(index, 1)
}
}

Or better yet, pass removal methods an index:

methods: {
removeTodo: function (index) {
this.todos.splice(index, 1)
}
}

Upgrade Path

Run the migration helper on your codebase to find examples of .$remove on an array. If you miss any, you should see console errors from the missing method.

Vue.set and Vue.delete on Vue instances removed

Vue.set and Vue.delete can no longer work on Vue instances. It is now mandatory to properly declare all top-level reactive properties in the data option. If you’d like to delete properties on a Vue instance or its $data, set it to null.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.set or Vue.delete on a Vue instance. If you miss any, they'll trigger console warnings.

Replacing vm.$data removed

It is now prohibited to replace a component instance’s root $data. This prevents some edge cases in the reactivity system and makes the component state more predictable (especially with type-checking systems).

Upgrade Path

Run the migration helper on your codebase to find examples of overwriting vm.$data. If you miss any, console warnings will be emitted.

vm.$get removed

Instead, retrieve reactive data directly.

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$get. If you miss any, you'll see console errors.

DOM-Focused Instance Methods

vm.$appendTo removed

Use the native DOM API:

myElement.appendChild(vm.$el)

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$appendTo. If you miss any, you'll see console errors.

vm.$before removed

Use the native DOM API:

myElement.parentNode.insertBefore(vm.$el, myElement)

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$before. If you miss any, you'll see console errors.

vm.$after removed

Use the native DOM API:

myElement.parentNode.insertBefore(vm.$el, myElement.nextSibling)

Or if myElement is the last child:

myElement.parentNode.appendChild(vm.$el)

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$after. If you miss any, you'll see console errors.

vm.$remove removed

Use the native DOM API:

vm.$el.remove()

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$remove. If you miss any, you'll see console errors.

Meta Instance Methods

vm.$eval removed

No real use. If you do happen to rely on this feature somehow and aren’t sure how to work around it, post on the forum for ideas.

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$eval. If you miss any, you'll see console errors.

vm.$interpolate removed

No real use. If you do happen to rely on this feature somehow and aren’t sure how to work around it, post on the forum for ideas.

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$interpolate. If you miss any, you'll see console errors.

vm.$log removed

Use the Vue Devtools for the optimal debugging experience.

Upgrade Path

Run the migration helper on your codebase to find examples of vm.$log. If you miss any, you'll see console errors.

Instance DOM Options

replace: false removed

Components now always replace the element they’re bound to. To simulate the behavior of replace: false, you can wrap your root component with an element similar to the one you’re replacing. For example:

new Vue({
el: '#app',
template: '<div id="app"> ... </div>'
})

Or with a render function:

new Vue({
el: '#app',
render: function (h) {
h('div', {
attrs: {
id: 'app',
}
}, /* ... */)
}
})

Upgrade Path

Run the migration helper on your codebase to find examples of replace: false.

Global Config

Vue.config.debug removed

No longer necessary, since warnings come with stack traces by default now.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.config.debug.

Vue.config.async removed

Async is now required for rendering performance.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.config.async.

Vue.config.delimiters replaced

This has been reworked as a component-level option. This allows you to use alternative delimiters within your app without breaking 3rd-party components.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.config.delimiters.

Vue.config.unsafeDelimiters removed

HTML interpolation has been removed in favor of v-html.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.config.unsafeDelimiters. After this, the helper will also find instances of HTML interpolation so that you can replace them with `v-html`.

Global API

Vue.extend with el removed

The el option can no longer be used in Vue.extend. It’s only valid as an instance creation option.

Upgrade Path

Run your end-to-end test suite or app after upgrading and look for console warnings about the el option with Vue.extend.

Vue.elementDirective removed

Use components instead.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.elementDirective.

Vue.partial removed

Partials have been removed in favor of more explicit data flow between components, using props. Unless you’re using a partial in a performance-critical area, the recommendation is to use a normal component instead. If you were dynamically binding the name of a partial, you can use a dynamic component.

If you happen to be using partials in a performance-critical part of your app, then you should upgrade to functional components. They must be in a plain JS/JSX file (rather than in a .vue file) and are stateless and instanceless, like partials. This makes rendering extremely fast.

A benefit of functional components over partials is that they can be much more dynamic, because they grant you access to the full power of JavaScript. There is a cost to this power however. If you’ve never used a component framework with render functions before, they may take a bit longer to learn.

Upgrade Path

Run the migration helper on your codebase to find examples of Vue.partial.