A linter reads your code and points at patterns that are probably bugs, or that break the style your team agreed on. ESLint is the most widely used linter for JavaScript, and eslint-plugin-vue is the plugin that teaches it to read .vue files. Version 10.0.0 of the plugin came out on 5 March 2025.
To upgrade, remove or rename the rules this release dropped, change .eslintrc preset names if you still use them, and fix a few new warnings. If you are starting fresh, the second half sets up a Vue 3 TypeScript project: a flat eslint.config.js, a pnpm lint script and an editor that underlines the same problems while you type.
What shipped on 5 March
The release notes open with the new minimum versions:
- Node.js
^18.18.0 || ^20.9.0 || >=21.1.0 - ESLint
^8.57.0 || ^9.0.0
ESLint 9.21 is the current release, and the rest of this post uses it. ESLint 8.57 still works with the plugin, but flat config, the single eslint.config.js file that replaced .eslintrc, has been ESLint's default since version 9.
If you still use .eslintrc, two things change. The presets now match their flat config names, so the Vue 3 ones lose their vue3- prefix: plugin:vue/vue3-recommended becomes plugin:vue/recommended, and likewise for essential and strongly-recommended. Those unprefixed names used to mean Vue 2, so a Vue 2 project must switch to plugin:vue/vue2-recommended and its siblings. And the vue/setup-compiler-macros environment is gone; vue-eslint-parser 9 and later knows defineProps and the other compiler macros without it. Moving to flat config now saves you doing the rename twice.
There is also a new versioning policy: minor releases may add checks for new Vue and Nuxt features, and may update the list of deprecated or reserved HTML and SVG tag names. So a minor update can report problems the previous one did not. Read the notes before you bump the plugin, and keep it pinned in your lockfile.
Removed rules now stop ESLint
The six rules deprecated during version 9 are gone. Four have a direct replacement, so rename them:
vue/component-tags-order→vue/block-ordervue/no-ref-object-destructure→vue/no-ref-object-reactivity-lossvue/no-setup-props-destructure→vue/no-setup-props-reactivity-lossvue/v-on-function-call→vue/v-on-handler-style
Delete the other two. vue/script-setup-uses-vars has no replacement. vue/no-invalid-model-keys points to vue/valid-model-definition, which checks the Vue 2 model option and is itself deprecated in this release.
If your flat config still turns on a removed rule, ESLint 9 stops with Key "rules": Key "vue/component-tags-order": Could not find "component-tags-order" in plugin "vue". On .eslintrc it reports Definition for rule 'vue/component-tags-order' was not found instead. Either way, rename or delete the rule and run again.
New rules, and browser globals you now set yourself
Five rules joined the presets:
- In the recommended set, as warnings:
vue/block-order(the order of the<script>,<template>and<style>blocks) andvue/no-required-prop-with-default(a prop that has a default value should not also be required). - In the Vue 3 essential set, as errors:
vue/no-deprecated-delete-setandvue/no-deprecated-model-definition, which flag Vue 2 APIs that no longer exist in Vue 3, andvue/valid-define-options, which checks thatdefineOptions()is called correctly.
One new rule is not in any preset: vue/no-import-compiler-macros reports import { defineProps } from 'vue'. Inside <script setup>, defineProps, defineEmits, defineModel and the other compiler macros need no import, because the compiler replaces them. Importing one is a common first mistake, which Vue's compiler answers by dropping the import with a warning. Turn the rule on, and --fix deletes the import for you.
Smaller changes matter only if you configured these rules: vue/prefer-true-attribute-shorthand gained an except option, vue/order-in-components now places slots and expose in its default order, vue/custom-event-name-casing lost its legacy option and vue/sort-keys lost runOutsideVue.
The presets no longer declare browser globals. If you lint plain JavaScript with no-undef and relied on the plugin to know about window and document, add the globals package and spread globals.browser into languageOptions.globals. In the TypeScript setup below this does not matter, because no-undef is switched off for TypeScript and .vue files: the TypeScript compiler already reports unknown names, and with better accuracy.
A full flat config for Vue 3 and TypeScript
Install four packages. @vue/eslint-config-typescript is the config create-vue uses to combine eslint-plugin-vue with typescript-eslint. Version 14.5.0, published on 6 March, is the first that accepts eslint-plugin-vue 10 in its peer range. It needs ESLint 9.10 or later and works only with flat config.
pnpm add -D eslint eslint-plugin-vue @vue/eslint-config-typescript typescript
Then create eslint.config.js at the project root, next to package.json. The project needs "type": "module" in package.json, which a create-vue project already has.
import pluginVue from 'eslint-plugin-vue'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
export default defineConfigWithVueTs(
{ files: ['**/*.{ts,mts,tsx,vue}'] },
{ ignores: ['dist/**', 'coverage/**'] },
pluginVue.configs['flat/recommended'],
vueTsConfigs.recommended,
{
rules: {
'vue/block-order': ['error', { order: ['script', 'template', 'style'] }],
'vue/no-import-compiler-macros': 'error',
},
},
)
Read it top to bottom:
- The first object says which files to lint.
- The second says which folders to skip; build output should never be linted.
pluginVue.configs['flat/recommended']enables every Vue rule up to the "recommended" level: the essential rules that catch errors, the strongly recommended rules for readability, and the community style rules. If that is too opinionated for an existing codebase, start with'flat/essential'and move up later.vueTsConfigs.recommendedadds the typescript-eslint recommended rules and applies them inside.vuefiles too.- The last object holds your own choices: script first in every file (the default accepts
<script>and<template>in either order), and the new macro rule.
defineConfigWithVueTs() wraps all of it so the two parsers cooperate: vue-eslint-parser reads the file, and the TypeScript parser reads what is inside <script setup lang="ts">. It expects every <script> block in a .vue file to say lang="ts", and reports a vue/block-lang error for one that does not.
Add the scripts:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
No --ext .vue is needed. The preset tells ESLint which file patterns it owns, so eslint . finds every .vue file on its own. Run pnpm lint and you get a list of file paths, line numbers and rule names. Run pnpm lint:fix and ESLint rewrites what it can fix safely, such as block order and attribute shorthand.
Make props with a default optional
vue/no-required-prop-with-default is the new preset rule that asks you to change your types. A prop with a default can never be missing, so marking it required is a contradiction, and the type tells callers they must pass something they need not pass.
In the <script setup lang="ts"> block of Avatar.vue, size is required in the type and also has a default:
const props = withDefaults(defineProps<{ size: 'sm' | 'md' | 'lg' }>(), {
size: 'md',
})
const props = withDefaults(defineProps<{ size?: 'sm' | 'md' | 'lg' }>(), {
size: 'md',
})
The fix is the question mark: size? makes the prop optional in the type, the default fills it in at runtime, and props.size is still typed as the union, never undefined.
The rule reports Prop "size" should be optional. and offers the question mark as a suggestion, which your editor shows as a quick fix. eslint --fix applies it only if you turn on the rule's autofix option: 'vue/no-required-prop-with-default': ['warn', { autofix: true }].
Make the editor show the same errors
Install the ESLint extension (dbaeumer.vscode-eslint) in VS Code. The user guide notes that it checks JavaScript by default, so tell it about Vue files and let it fix on save:
{
"eslint.validate": ["javascript", "typescript", "vue"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
Commit this file. Everyone who opens the project then sees the same underlines you do, which is the whole point of a shared config.
Use the @nuxt/eslint module in Nuxt projects
If you are on Nuxt, do not write the config above by hand. The @nuxt/eslint module reached 1.0 on 30 January and generates a config that already knows about auto-imports, the pages/ and server/ folders and your modules. Run npx nuxi module add eslint and start the dev server; the module then writes an eslint.config.mjs at the project root that comes down to two lines:
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt()
Your own rules go inside withNuxt({ rules: { ... } }). The module brings its own copy of eslint-plugin-vue, so you upgrade the plugin by upgrading the module. The current release, @nuxt/eslint 1.1.0, still uses eslint-plugin-vue 9, so the changes above reach a Nuxt project only when a module release moves to version 10.
Upgrading an existing project
- Bump the plugin with
pnpm up eslint-plugin-vue@10. If you use@vue/eslint-config-typescript, update it to 14.5.0 as well; earlier versions do not accept plugin 10. - Run
pnpm lint. An error that a rule or preset cannot be found means an old name in your config; rename or delete it as shown above. - Run
pnpm lint:fixto put blocks in order and review the diff. Add the missing question marks forvue/no-required-prop-with-defaultwith your editor's quick fix. - Anything that remains is a real finding. Fix it, or disable the rule for that line with a comment that says why. Then commit the lockfile.