---
title: "Validate Plugin"
description: "Surface SEO and performance issues at runtime with the unhead ValidatePlugin: cross-tag conflicts, byte budgets, and crawler-aware checks."
canonical_url: "https://unhead.unjs.io/docs/typescript/head/guides/tooling/validate-plugin"
last_updated: "2026-05-14T22:16:52.177Z"
---

`ValidatePlugin` is the runtime arm of unhead's validation story. Where the [ESLint plugin](/docs/typescript/head/guides/tooling/eslint-plugin) sees source files and the [CLI](/docs/typescript/head/guides/tooling/cli) sees rendered HTML, the runtime plugin sees the resolved tag set during a real render — which is the only place to catch things like cross-tag conflicts, byte budgets, and tags that are missing because a plugin wasn't registered.

## When to use it

- **Development**: register the plugin in dev to get live warnings in the console as you change tags.
- **CI** *(via the CLI validate-html / validate-url commands)*: the CLI loads `ValidatePlugin` over your prerendered output or a live URL and exits non-zero on issues.

You generally don't want to ship the plugin in production: most of its checks are best-effort heuristics that aren't worth the per-render cost in user-facing traffic.

## Usage

```ts
import { createHead } from 'unhead'
import { ValidatePlugin } from 'unhead/plugins'

const head = createHead({
  plugins: [
    ValidatePlugin(),
  ],
})
```

By default, every rule reports through `console.warn` with the source file and line of the `useHead` call that pushed the offending tag.

## Options

```ts
ValidatePlugin({
  // Override the per-rule severity. Accepts 'warn' | 'info' | 'off'
  // or, for the rules below, a tuple of [severity, options].
  rules: {
    'missing-description': 'off',
    'too-many-preloads': ['warn', { max: 10 }],
    'inline-style-size': ['info', { maxKB: 20 }],
  },
  // Replace the default console.warn dispatch.
  onReport(rules) {
    for (const r of rules)
      myLogger.warn(r.id, r.message, r.source)
  },
  // Project root, used to render source locations as relative paths.
  root: process.cwd(),
})
```

### Configurable rules

A small set of rules accept a `[severity, options]` tuple to tune their thresholds:

<table>
<thead>
  <tr>
    <th>
      Rule
    </th>
    
    <th>
      Option
    </th>
    
    <th>
      Default
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        charset-not-early
      </code>
    </td>
    
    <td>
      <code>
        maxPosition
      </code>
    </td>
    
    <td>
      3
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inline-script-size
      </code>
    </td>
    
    <td>
      <code>
        maxKB
      </code>
    </td>
    
    <td>
      2
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inline-style-size
      </code>
    </td>
    
    <td>
      <code>
        maxKB
      </code>
    </td>
    
    <td>
      14
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        meta-beyond-1mb
      </code>
    </td>
    
    <td>
      <code>
        maxBytes
      </code>
    </td>
    
    <td>
      1_048_576
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        too-many-fetchpriority-high
      </code>
    </td>
    
    <td>
      <code>
        max
      </code>
    </td>
    
    <td>
      2
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        too-many-preloads
      </code>
    </td>
    
    <td>
      <code>
        max
      </code>
    </td>
    
    <td>
      6
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        too-many-preconnects
      </code>
    </td>
    
    <td>
      <code>
        max
      </code>
    </td>
    
    <td>
      4
    </td>
  </tr>
</tbody>
</table>

## Rule IDs

Every rule has a stable string ID (the same one the [ESLint plugin](/docs/typescript/head/guides/tooling/eslint-plugin) uses when it covers the same check at the source level). The full list is exported as a runtime array:

```ts
import { VALIDATION_RULE_IDS } from 'unhead/validate'

console.log(VALIDATION_RULE_IDS) // ['canonical-og-url-mismatch', 'charset-not-early', ...]
```

`unhead/validate` also exports `KNOWN_META_NAMES`, `KNOWN_META_PROPERTIES`, and `URL_META_KEYS` — the same allowlists the runtime plugin and the eslint plugin read from for typo detection. Useful if you're building tooling on top of unhead.

## Integration with devtools

When the unhead Vite plugin is active, every rule reported by `ValidatePlugin` shows up in the devtools `Tags` and `Audit` panels. Each row links back to the file and line that pushed the tag (via Vite's open-in-editor endpoint), and per-rule severity overrides let you silence noisy rules without changing your runtime config.
