---
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-08-11T00:38:16.870Z"
---

`ValidatePlugin` checks the resolved tag set during rendering. Unlike source lint, it can report cross-tag conflicts, rendered byte budgets, and tags missing from the final output. The [CLI](/docs/typescript/head/guides/tooling/cli) uses the same rules when validating rendered HTML.

## 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.

Register the plugin during development. Its heuristic checks add work to each render and are not intended for production traffic.

## Usage

```ts
import { createHead } from 'unhead/client'
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 }],
    'too-many-prefetches': ['info', { max: 100 }],
    '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)
  },
})
```

The optional `root` setting shortens captured source locations relative to an absolute project path. Supply it through build-time configuration; browser code does not have Node's `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-prefetches
      </code>
    </td>
    
    <td>
      <code>
        max
      </code>
    </td>
    
    <td>
      50
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        too-many-preconnects
      </code>
    </td>
    
    <td>
      <code>
        max
      </code>
    </td>
    
    <td>
      4
    </td>
  </tr>
</tbody>
</table>

The `too-many-prefetches` default is an advisory guardrail for speculative bandwidth and cache use, not a browser or standards limit.

## Rule IDs

Every rule has a stable string ID. The [ESLint plugin](/docs/typescript/head/guides/tooling/eslint-plugin) uses the same IDs for equivalent source-level checks. 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 the `KNOWN_META_NAMES`, `KNOWN_META_PROPERTIES`, and `URL_META_KEYS` allowlists used for typo detection. These exports are available to other tooling.

`deprecated-twitter-meta` reports non-Slack `twitter:*` metadata and recommends Open Graph metadata. The Slack unfurl fields `twitter:label1`, `twitter:label2`, `twitter:data1`, and `twitter:data2` are excluded.

## Integration with DevTools

When the Unhead Vite plugin is active, reports from `ValidatePlugin` appear in the DevTools **Tags** and **Audit** panels. Each row links to the file and line that pushed the tag through Vite's open-in-editor endpoint.
