---
title: "eslint-plugin-vue 10 and flat config: a clean lint setup for Vue · vuejs.school"
description: "eslint-plugin-vue 10 drops the rules deprecated in version 9 and adds five to its presets. Here is a full flat config for a Vue 3 TypeScript project."
canonical_url: "https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config"
meta:
  "og:description": "eslint-plugin-vue 10 drops the rules deprecated in version 9 and adds five to its presets. Here is a full flat config for a Vue 3 TypeScript project."
  "og:image": "https://vuejs.school/_og/d/a_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZQ%2Cdescription_eslint-plugin-vue%2B10%2Bdrops%2Bthe%2Brules%2Bdeprecated%2Bin%2Bversion%2B9%2Band%2Badds%2Bfive%2Bto%2Bits%2Bpresets.%2BHere%2Bis%2Ba%2Bfull%2Bflat%2Bconfig%2Bfor%2Ba%2BVue%2B3%2BTypeScript%2Bproject.%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9lc2xpbnQtcGx1Z2luLXZ1ZS0xMC1hbmQtZmxhdC1jb25maWci%2Cs_T-GYFQXbu7bzoC2A.png"
  "og:image:alt": "eslint-plugin-vue 10 and flat config: a clean lint setup for Vue · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "eslint-plugin-vue 10 and flat config: a clean lint setup for Vue · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZQ%2Cdescription_eslint-plugin-vue%2B10%2Bdrops%2Bthe%2Brules%2Bdeprecated%2Bin%2Bversion%2B9%2Band%2Badds%2Bfive%2Bto%2Bits%2Bpresets.%2BHere%2Bis%2Ba%2Bfull%2Bflat%2Bconfig%2Bfor%2Ba%2BVue%2B3%2BTypeScript%2Bproject.%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9lc2xpbnQtcGx1Z2luLXZ1ZS0xMC1hbmQtZmxhdC1jb25maWci%2Cs_T-GYFQXbu7bzoC2A.png"
  "twitter:image:alt": "eslint-plugin-vue 10 and flat config: a clean lint setup for Vue · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What shipped on 5 March

The [release notes](https://github.com/vuejs/eslint-plugin-vue/releases/tag/v10.0.0) 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-order`
- `vue/no-ref-object-destructure` → `vue/no-ref-object-reactivity-loss`
- `vue/no-setup-props-destructure` → `vue/no-setup-props-reactivity-loss`
- `vue/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) and `vue/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-set` and `vue/no-deprecated-model-definition`, which flag Vue 2 APIs that no longer exist in Vue 3, and `vue/valid-define-options`, which checks that `defineOptions()` 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.

*eslint.config.js*

```
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.recommended` adds the typescript-eslint recommended rules and applies them inside `.vue` files 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:

*package.json*

```
{
  "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:

*Avatar.vue**Breaks***

```
const props = withDefaults(defineProps<{ size: 'sm' | 'md' | 'lg' }>(), {
  size: 'md',
})
```

*Avatar.vue**Works***

```
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](https://eslint.vuejs.org/rules/no-required-prop-with-default.html): `'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](https://eslint.vuejs.org/user-guide/) notes that it checks JavaScript by default, so tell it about Vue files and let it fix on save:

*.vscode/settings.json*

```
{
  "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:

*eslint.config.mjs*

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

1. 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.
2. 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.
3. Run `pnpm lint:fix` to put blocks in order and review the diff. Add the missing question marks for `vue/no-required-prop-with-default` with your editor's quick fix.
4. 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.

**Check it stuck**3 questions from the Linting and formatting quiz.

[**Practice now**](https://vuejs.school/learn/tooling/linting-and-formatting?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Tutorial · 8 min read**Editor and Vue DevTools setup in 2026**](https://vuejs.school/community/blog/editor-and-vue-devtools-setup-in-2026) [Release notes · 6 min read**Rolldown 1.0: the bundler under Vite 8 is stable**](https://vuejs.school/community/blog/rolldown-1-0-the-bundler-under-vite-8-is-stable)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-06","description":"eslint-plugin-vue 10 drops the rules deprecated in version 9 and adds five to its presets. Here is a full flat config for a Vue 3 TypeScript project.","name":"eslint-plugin-vue 10 and flat config: a clean lint setup for Vue","url":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config","breadcrumb":{"@id":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-06","description":"eslint-plugin-vue 10 drops the rules deprecated in version 9 and adds five to its presets. Here is a full flat config for a Vue 3 TypeScript project.","headline":"eslint-plugin-vue 10 and flat config: a clean lint setup for Vue","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZQ%2Cdescription_eslint-plugin-vue%2B10%2Bdrops%2Bthe%2Brules%2Bdeprecated%2Bin%2Bversion%2B9%2Band%2Badds%2Bfive%2Bto%2Bits%2Bpresets.%2BHere%2Bis%2Ba%2Bfull%2Bflat%2Bconfig%2Bfor%2Ba%2BVue%2B3%2BTypeScript%2Bproject.%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9lc2xpbnQtcGx1Z2luLXZ1ZS0xMC1hbmQtZmxhdC1jb25maWci%2Cs_T-GYFQXbu7bzoC2A.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/eslint-plugin-vue-10-and-flat-config#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"eslint-plugin-vue 10 and flat config: a clean lint setup for Vue","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZQ%2Cdescription_eslint-plugin-vue%2B10%2Bdrops%2Bthe%2Brules%2Bdeprecated%2Bin%2Bversion%2B9%2Band%2Badds%2Bfive%2Bto%2Bits%2Bpresets.%2BHere%2Bis%2Ba%2Bfull%2Bflat%2Bconfig%2Bfor%2Ba%2BVue%2B3%2BTypeScript%2Bproject.%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9lc2xpbnQtcGx1Z2luLXZ1ZS0xMC1hbmQtZmxhdC1jb25maWci%2Cs_T-GYFQXbu7bzoC2A.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~ZXNsaW50LXBsdWdpbi12dWUgMTAgYW5kIGZsYXQgY29uZmlnOiBhIGNsZWFuIGxpbnQgc2V0dXAgZm9yIFZ1ZQ%2Cdescription_eslint-plugin-vue%2B10%2Bdrops%2Bthe%2Brules%2Bdeprecated%2Bin%2Bversion%2B9%2Band%2Badds%2Bfive%2Bto%2Bits%2Bpresets.%2BHere%2Bis%2Ba%2Bfull%2Bflat%2Bconfig%2Bfor%2Ba%2BVue%2B3%2BTypeScript%2Bproject.%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy9lc2xpbnQtcGx1Z2luLXZ1ZS0xMC1hbmQtZmxhdC1jb25maWci%2Cs_T-GYFQXbu7bzoC2A.png"}]}
```