---
title: "ESLint Plugin"
description: "Catch unhead misuse, type-narrowing issues, and v2-to-v3 migration problems at the source level with @unhead/eslint-plugin."
canonical_url: "https://unhead.unjs.io/docs/typescript/head/guides/tooling/eslint-plugin"
last_updated: "2026-05-14T22:16:22.093Z"
---

`@unhead/eslint-plugin` is a flat-config ESLint plugin that surfaces unhead-specific issues in your editor and CI before they reach a browser. It mirrors a subset of the runtime [`ValidatePlugin`](/docs/typescript/head/guides/tooling/validate-plugin) rules at the AST level — so deprecated v2 props, missing required attributes, and meta-tag typos get caught the moment you save.

## Why use it?

The runtime plugin reports issues only when a head is rendered, which means you only see them while running the app. The lint rules see your source files directly:

- **Faster feedback loop**: squigglies in your editor, not warnings in your dev console.
- **Autofixable rewrites**: `children` → `innerHTML`, `hid`/`vmid` → `key`, `body: true` → `tagPosition: 'bodyClose'`, missing `crossorigin` on font preloads, missing `@` on Twitter handles, redundant `defer` on module scripts. Run once and you've migrated the bulk of a v2 codebase.
- **Shared rule IDs**: lint, runtime, and CLI all read from `unhead/validate`, so a rule turning on or off behaves the same across every layer.

## Install

```bash
pnpm add -D @unhead/eslint-plugin
```

## Usage

Add the recommended config to your flat ESLint config:

```ts [eslint.config.ts]
import unhead from '@unhead/eslint-plugin'

export default [
  unhead.configs.recommended,
]
```

For projects migrating from unhead v2, swap in `unhead.configs.migration` to also wrap tag literals in their `defineLink` / `defineScript` helpers for type narrowing.

```ts [eslint.config.ts]
import unhead from '@unhead/eslint-plugin'

export default [
  unhead.configs.migration,
]
```

## Rules

The plugin ships 14 rules. The full table with severity, autofix status, and what each rule catches lives in the package README:

→ [`@unhead/eslint-plugin` rules table](https://github.com/unjs/unhead/tree/main/packages/eslint-plugin#rules)

The most impactful ones for migration:

- `no-deprecated-props` (error, autofix) — rewrites all v2 prop names that v3 no longer auto-converts.
- `no-unknown-meta` (warn, autofix) — Levenshtein-suggested fix for typos like `og:descriptin` → `og:description`.
- `numeric-tag-priority` (warn, suggestions) — flags raw numeric `tagPriority` values, suggests `'critical' | 'high' | 'low'`.
- `prefer-define-helpers` (off in `recommended`, on in `migration`, autofix) — wraps `link` / `script` tag literals in `defineLink` / `defineScript` so unhead's discriminated-union types narrow.

## What it can and can't see

Lint rules walk source-level calls into `useHead`, `useHeadSafe`, `useServerHead`, `useServerHeadSafe`, `useSeoMeta`, `useServerSeoMeta`, and the tag helpers `defineLink` / `defineScript`. Tag arrays inside `meta` / `link` / `script` / `noscript` / `style` keys are descended automatically.

It cannot see anything that depends on the resolved tag set: cross-tag conflicts (canonical vs `og:url`), counts (too many preloads), or rendered byte budgets (meta beyond 1MB). Those checks live in the runtime [`ValidatePlugin`](/docs/typescript/head/guides/tooling/validate-plugin) and are surfaced by the [CLI](/docs/typescript/head/guides/tooling/cli)'s `validate-html` and `validate-url` commands.

## Editor integration

If your editor runs ESLint via the official VS Code/JetBrains plugin, autofixable rules show "Fix all auto-fixable problems" actions and `numeric-tag-priority` shows suggestion picks for each alias. No additional setup beyond installing the plugin.
