Migrate to v3 · Unhead

[Unhead Home](https://unhead.unjs.io/ "Home")

- [Docs](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)
- [Tools](https://unhead.unjs.io/tools)
- [Learn](https://unhead.unjs.io/learn/guides/what-is-capo)

[Releases](https://unhead.unjs.io/releases)

Search…```k`` /`

[Unhead on GitHub](https://github.com/unjs/unhead)

[User Guides](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)

[API](https://unhead.unjs.io/docs/typescript/head/api/get-started/overview)

[Releases](https://unhead.unjs.io/docs/typescript/releases/v3)

TypeScript

- [Switch to TypeScript](https://unhead.unjs.io/docs/typescript/migration-guide/v3)
- [Switch to Vue](https://unhead.unjs.io/docs/vue/migration-guide/v3)
- [Switch to React](https://unhead.unjs.io/docs/react/migration-guide/v3)
- [Switch to Svelte](https://unhead.unjs.io/docs/svelte/migration-guide/v3)
- [Switch to Solid.js](https://unhead.unjs.io/docs/solid-js/migration-guide/v3)
- [Switch to Angular](https://unhead.unjs.io/docs/angular/migration-guide/v3)
- [Switch to Nuxt](https://unhead.unjs.io/docs/nuxt/migration-guide/v3)

v3 (stable)

Head

- [Discord Support](https://discord.com/invite/275MBUBvgP)
- [TypeScript Playground](https://stackblitz.com/edit/github-hhxywsb5)

- Releases
  - [v3](https://unhead.unjs.io/docs/typescript/releases/v3)
  - [v2](https://unhead.unjs.io/docs/typescript/releases/v2)
- Migration Guide
  - [v3](https://unhead.unjs.io/docs/typescript/migration-guide/v3)
  - [v2](https://unhead.unjs.io/docs/typescript/migration-guide/v2)

Migration Guide

# Migrate to v3

[Copy for LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/6.migration-guide/1.v3.md)

Last updated Apr 9, 2026 by [Harlan Wilton](https://github.com/harlan-zw) in [refactor(bundler)!: named Unhead export, ctx-based transforms, dev-mode validate (#733)](https://github.com/unjs/unhead/pull/733).

On this page

- [Automated Migration Checks](#automated-migration-checks)
- [@unhead/addons → @unhead/bundler](#unheadaddons-unheadbundler)
- [Framework Vite Plugins: Named Unhead Export](#framework-vite-plugins-named-unhead-export)
- [Legacy Property Names](#legacy-property-names)
- [Schema.org Plugin](#schemaorg-plugin)
- [Server Composables Removed](#server-composables-removed)
- [Core API Changes](#core-api-changes)
- [Vue Legacy Exports](#vue-legacy-exports)
- [Server Utilities](#server-utilities)
- [Hooks](#hooks)
- [Type Changes](#type-changes)
- [Strict Type Narrowing for Link, Script, and Meta](#strict-type-narrowing-for-link-script-and-meta)
- [Other Removed APIs](#other-removed-apis)
- [Quick Reference: Import Changes](#quick-reference-import-changes)

Unhead v3 removes all deprecated APIs and focuses on performance improvements. This guide covers the breaking changes.

## [Automated Migration Checks](#automated-migration-checks)

Add `ValidatePlugin` during your upgrade to automatically detect v2 patterns and get actionable warnings:

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

const head = createHead({
  plugins: [
    ValidatePlugin() // Detects deprecated props, missing plugins, and more
  ]
})
```

The plugin will warn you about:

- **Missing `TemplateParamsPlugin`** : template params like `%siteName` are now opt-in and will appear literally without the plugin
- **Missing `AliasSortingPlugin`** : `before:`/`after:` tag priorities are now opt-in and will be silently ignored without the plugin
- **Deprecated property names** : `children`, `hid`, `vmid`, `body: true` are no longer auto-converted
- **Removed `mode` option** : `{ mode: 'server' }` on `head.push()` is silently ignored

All rules use ESLint-style config and can be individually disabled:

```
ValidatePlugin({
  rules: {
    'missing-template-params-plugin': 'off',
  }
})
```

If you're using the [unified Vite plugin](https://unhead.unjs.io/docs/head/guides/build-plugins/overview), `ValidatePlugin` is automatically injected in dev so warnings surface in your browser console without any manual setup.

Remove `ValidatePlugin` once your migration is complete, or keep it for ongoing validation.

---

## [`@unhead/addons` → `@unhead/bundler`](#unheadaddons-unheadbundler)

🚦 Impact Level: **High** (only if you import build plugins manually)

The `@unhead/addons` package has been renamed to `@unhead/bundler`. The old package still works as a deprecation shim that re-exports from `@unhead/bundler`, but logs a runtime warning.

```
- pnpm add -D @unhead/addons
+ pnpm add -D @unhead/bundler
```

The default export has also been replaced with a named `Unhead` export:

```
- import unhead from '@unhead/addons/vite'
+ import { Unhead } from '@unhead/bundler/vite'

export default defineConfig({
- plugins: [unhead()],
+ plugins: [Unhead()],
})
```

Most users should import from their framework's vite subpath instead, which forwards to `@unhead/bundler` and wires up framework-specific runtime plugins:

```
import { Unhead } from '@unhead/vue/vite'
// or @unhead/react/vite, @unhead/svelte/vite, @unhead/solid-js/vite
```

Webpack consumers should update similarly:

```
- import unhead from '@unhead/addons/webpack'
+ import { Unhead } from '@unhead/bundler/webpack'
```

The minify backend subpaths have moved too:

```
- import { createJSMinifier } from '@unhead/addons/minify/rolldown'
- import { createCSSMinifier } from '@unhead/addons/minify/lightningcss'
+ import { createJSMinifier } from '@unhead/bundler/minify/rolldown'
+ import { createCSSMinifier } from '@unhead/bundler/minify/lightningcss'
```

See the [Build Plugins overview](https://unhead.unjs.io/docs/head/guides/build-plugins/overview) for the new options table.

---

## [Framework Vite Plugins: Named `Unhead` Export](#framework-vite-plugins-named-unhead-export)

🚦 Impact Level: **High**

Every framework Vite plugin now exports a named `Unhead` symbol instead of a default export. Update your `vite.config.ts`:

```
// Vue
- import unhead from '@unhead/vue/vite'
+ import { Unhead } from '@unhead/vue/vite'

// React
- import unhead from '@unhead/react/vite'
+ import { Unhead } from '@unhead/react/vite'

// Svelte
- import unhead from '@unhead/svelte/vite'
+ import { Unhead } from '@unhead/svelte/vite'

// Solid
- import unhead from '@unhead/solid-js/vite'
+ import { Unhead } from '@unhead/solid-js/vite'

export default defineConfig({
- plugins: [unhead()],
+ plugins: [Unhead()],
})
```

The plugin behaviour is otherwise unchanged. Nuxt users do not need to update anything.

---

## [Legacy Property Names](#legacy-property-names)

🚦 Impact Level: **High**

The `DeprecationsPlugin` that automatically converted legacy property names has been removed. You must update your head entries to use the current property names.

### [`children` → `innerHTML`](#children-innerhtml)

```
useHead({
  script: [{
-   children: 'console.log("hello")',
+   innerHTML: 'console.log("hello")',
  }]
})
```

### [`hid` / `vmid` → `key`](#hid-vmid-key)

```
useHead({
  meta: [{
-   hid: 'description',
+   key: 'description',
    name: 'description',
    content: 'My description'
  }]
})
```

```
useHead({
  meta: [{
-   vmid: 'og:title',
+   key: 'og:title',
    property: 'og:title',
    content: 'My Title'
  }]
})
```

### [`body: true` → `tagPosition: 'bodyClose'`](#body-true-tagposition-bodyclose)

```
useHead({
  script: [{
    src: '/script.js',
-   body: true,
+   tagPosition: 'bodyClose',
  }]
})
```

### [Quick Reference](#quick-reference)

| Old Property | New Property |
| --- | --- |
| `children` | `innerHTML` |
| `hid` | `key` |
| `vmid` | `key` |
| `body: true` | `tagPosition: 'bodyClose'` |

---

## [Schema.org Plugin](#schemaorg-plugin)

🚦 Impact Level: **High**

The `PluginSchemaOrg` and `SchemaOrgUnheadPlugin` exports have been removed. Use `UnheadSchemaOrg` instead.

```
- import { PluginSchemaOrg } from '@unhead/schema-org'
+ import { UnheadSchemaOrg } from '@unhead/schema-org'

const head = createHead({
  plugins: [
-   PluginSchemaOrg()
+   UnheadSchemaOrg()
  ]
})
```

For Vue users:

```
- import { PluginSchemaOrg } from '@unhead/schema-org/vue'
+ import { UnheadSchemaOrg } from '@unhead/schema-org/vue'
```

### [Schema.org Config Options](#schemaorg-config-options)

The following config options have been removed:

| Removed Option | Replacement |
| --- | --- |
| `canonicalHost` | `host` |
| `canonicalUrl` | `path` + `host` |
| `position` | Use `tagPosition` on individual schema entries |
| `defaultLanguage` | Use `inLanguage` on schema nodes |
| `defaultCurrency` | Use `priceCurrency` on schema nodes |

```
UnheadSchemaOrg({
- canonicalHost: 'https://example.com',
- canonicalUrl: 'https://example.com/page',
+ host: 'https://example.com',
+ path: '/page',
})
```

---

## [Server Composables Removed](#server-composables-removed)

🚦 Impact Level: **Medium-High**

The `useServerHead`, `useServerHeadSafe`, and `useServerSeoMeta` composables have been removed. Use the standard composables instead.

```
- import { useServerHead, useServerSeoMeta } from 'unhead'
+ import { useHead, useSeoMeta } from 'unhead'

- useServerHead({ title: 'My Page' })
+ useHead({ title: 'My Page' })

- useServerSeoMeta({ description: 'My description' })
+ useSeoMeta({ description: 'My description' })
```

If you need server-only head management, use conditional logic:

```
if (import.meta.server) {
  useHead({ title: 'Server Only' })
}
```

---

## [Core API Changes](#core-api-changes)

🚦 Impact Level: **Medium**

### [`createHeadCore` → `createUnhead`](#createheadcore-createunhead)

```
- import { createHeadCore } from 'unhead'
+ import { createUnhead } from 'unhead'

- const head = createHeadCore()
+ const head = createUnhead()
```

### [`headEntries()` → `entries` Map](#headentries-entries-map)

```
- const entries = head.headEntries()
+ const entries = [...head.entries.values()]
```

### [`mode` Option Removed](#mode-option-removed)

The `mode` option on head entries has been removed. Runtime mode detection is no longer supported.

```
head.push({
  title: 'My Page',
- }, { mode: 'server' })
+ })
```

Use the appropriate `createHead` function instead:

```
// Client-side
import { createHead } from 'unhead/client'

// Server-side
import { createHead } from 'unhead/server'
```

---

## [Vue Legacy Exports](#vue-legacy-exports)

🚦 Impact Level: **Medium**

### [`/legacy` Export Path Deprecated](#legacy-export-path-deprecated)

The `@unhead/vue/legacy` import still works but emits a runtime deprecation warning. Update to the explicit client or server import:

```
- import { createHead } from '@unhead/vue/legacy'
+ import { createHead } from '@unhead/vue/client'
// or for SSR
+ import { createHead } from '@unhead/vue/server'
```

### [`createHeadCore` Removed](#createheadcore-removed)

```
- import { createHeadCore } from '@unhead/vue'
+ import { createHead } from '@unhead/vue/server'
// or for client
+ import { createHead } from '@unhead/vue/client'
```

---

## [Server Utilities](#server-utilities)

🚦 Impact Level: **Low**

### [`extractUnheadInputFromHtml` → `parseHtmlForUnheadExtraction`](#extractunheadinputfromhtml-parsehtmlforunheadextraction)

The function has been moved from `unhead/server` to `unhead/parser`.

```
- import { extractUnheadInputFromHtml } from 'unhead/server'
+ import { parseHtmlForUnheadExtraction } from 'unhead/parser'

- const { input } = extractUnheadInputFromHtml(html)
+ const { input } = parseHtmlForUnheadExtraction(html)
```

---

## [Hooks](#hooks)

🚦 Impact Level: **Low**

The following hooks have been removed:

- `init` : No longer needed
- `dom:renderTag` : DOM rendering is now synchronous
- `dom:rendered` : Use the `onRendered` option on `useHead()` instead

The `dom:beforeRender` hook is now synchronous and `renderDOMHead` no longer returns a Promise:

```
- await renderDOMHead(head, { document })
+ renderDOMHead(head, { document })
```

The SSR hooks (`ssr:beforeRender`, `ssr:render`, `ssr:rendered`) are now synchronous and `renderSSRHead` no longer returns a Promise:

```
- const head = await renderSSRHead(head)
+ const head = renderSSRHead(head)
```

---

## [Type Changes](#type-changes)

🚦 Impact Level: **Low**

| Removed Type | Replacement |
| --- | --- |
| `Head` | `HeadTag` or specific tag types |
| `ResolvedHead` | `ResolvedHeadTag` |
| `MergeHead` | Use generics directly |
| `MetaFlatInput` | `MetaFlat` |
| `ResolvedMetaFlat` | `MetaFlat` |
| `RuntimeMode` | Removed (no replacement needed) |

```
- import type { Head, MetaFlatInput, RuntimeMode } from 'unhead'
+ import type { HeadTag, MetaFlat } from 'unhead'
```

---

## [Strict Type Narrowing for Link, Script, and Meta](#strict-type-narrowing-for-link-script-and-meta)

🚦 Impact Level: **Medium**

The `Link` and `Script` types are now strict discriminated unions. Known `rel` and `type` values enforce per-tag required properties at the type level. `GenericLink` and `GenericScript` are still exported for custom values.

### [Link Tags](#link-tags)

Known `rel` values now enforce their required properties. For example, preloading a font requires `crossorigin`:

```
useHead({
  link: [{
    rel: 'preload',
    as: 'font',
    href: '/font.woff2',
+   crossorigin: 'anonymous', // now required for font preloads
  }]
})
```

For custom `rel` values not in the known set, use `satisfies GenericLink`:

```
import type { GenericLink } from 'unhead/types'

useHead(unheadInstance, {
  link: [
    { rel: 'me', href: 'https://mastodon.social/@me' } satisfies GenericLink,
  ]
})
```

### [Script Tags](#script-tags)

Inline scripts must have `textContent` or `innerHTML` and cannot include `src`, `async`, or `defer`. For custom `type` values, use `satisfies GenericScript`:

```
import type { GenericScript } from 'unhead/types'

useHead(unheadInstance, {
  script: [
    { type: 'text/plain', textContent: '...' } satisfies GenericScript,
  ]
})
```

### [Meta Content Required](#meta-content-required)

Meta `content` is now required on name, property, and http-equiv meta tags. Use `null` explicitly to remove a meta tag:

```
- useHead({ meta: [{ name: 'description' }] }) // no longer valid
+ useHead({ meta: [{ name: 'description', content: null }] }) // removes the tag
```

### [String Variables](#string-variables)

When `rel` or `type` comes from a variable typed as `string`, TypeScript cannot narrow the union. Use `as const` or `satisfies`:

```
import type { GenericLink } from 'unhead/types'

const rel = getRelFromConfig() // string, not a literal
useHead(unheadInstance, {
  link: [{ rel, href: '/path' } satisfies GenericLink]
})

// or use as const for literals
const link = { rel: 'canonical' as const, href: '/path' }
useHead(unheadInstance, { link: [link] })
```

---

## [Other Removed APIs](#other-removed-apis)

- `resolveScriptKey` : Internal utility, no longer exported
- `DeprecationsPlugin` : Update property names directly instead
- `resolveUnrefHeadInput` (Vue) : Reactive resolution now happens automatically
- `setHeadInjectionHandler` (Vue) : Head injection is handled automatically

---

## [Quick Reference: Import Changes](#quick-reference-import-changes)

```
// Build plugins
- import unhead from '@unhead/addons/vite'
+ import { Unhead } from '@unhead/bundler/vite'
// or, recommended, from your framework subpath:
+ import { Unhead } from '@unhead/vue/vite'

// Legacy properties - update property names directly, no plugin needed
- import { DeprecationsPlugin } from 'unhead/plugins'

// Schema.org
- import { PluginSchemaOrg, SchemaOrgUnheadPlugin } from '@unhead/schema-org'
+ import { UnheadSchemaOrg } from '@unhead/schema-org'

// Server composables
- import { useServerHead, useServerHeadSafe, useServerSeoMeta } from 'unhead'
+ import { useHead, useHeadSafe, useSeoMeta } from 'unhead'

// Core
- import { createHeadCore } from 'unhead'
+ import { createUnhead } from 'unhead'

// Server utilities
- import { extractUnheadInputFromHtml } from 'unhead/server'
+ import { parseHtmlForUnheadExtraction } from 'unhead/parser'

// Vue
- import { createHeadCore, resolveUnrefHeadInput, setHeadInjectionHandler } from '@unhead/vue'
- import { ... } from '@unhead/vue/legacy'
+ import { createHead } from '@unhead/vue/client'
+ import { createHead } from '@unhead/vue/server'
```

[Edit this page](https://github.com/unjs/unhead/edit/main/docs/6.migration-guide/1.v3.md)

[Markdown For LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/6.migration-guide/1.v3.md)

Did this page help you?

[Vue Components Schema.org Vue components API (deprecated). Use composables like useSchemaOrg() instead for better TypeScript support.](https://unhead.unjs.io/docs/vue/schema-org/guides/core-concepts/vue-components) [v2 Migrate from Unhead v1 to v2. Covers subpath exports, removed implicit context, opt-in plugins, and more.](https://unhead.unjs.io/docs/migration-guide/v2)

On this page

- [Automated Migration Checks](#automated-migration-checks)
- [@unhead/addons → @unhead/bundler](#unheadaddons-unheadbundler)
- [Framework Vite Plugins: Named Unhead Export](#framework-vite-plugins-named-unhead-export)
- [Legacy Property Names](#legacy-property-names)
- [Schema.org Plugin](#schemaorg-plugin)
- [Server Composables Removed](#server-composables-removed)
- [Core API Changes](#core-api-changes)
- [Vue Legacy Exports](#vue-legacy-exports)
- [Server Utilities](#server-utilities)
- [Hooks](#hooks)
- [Type Changes](#type-changes)
- [Strict Type Narrowing for Link, Script, and Meta](#strict-type-narrowing-for-link-script-and-meta)
- [Other Removed APIs](#other-removed-apis)
- [Quick Reference: Import Changes](#quick-reference-import-changes)

[GitHub](https://github.com/unjs/unhead) [ Discord](https://discord.com/invite/275MBUBvgP)

[ /llms.txt](https://unhead.unjs.io/llms.txt)

[Part of the UnJS ecosystem](https://unjs.io/)

### Head Management

- [Getting Started](https://unhead.unjs.io/docs/typescript/head/guides/get-started/overview)
- [useHead](https://unhead.unjs.io/docs/typescript/head/api/composables/use-head)
- [useSeoMeta](https://unhead.unjs.io/docs/typescript/head/api/composables/use-seo-meta)
- [useHeadSafe](https://unhead.unjs.io/docs/typescript/head/api/composables/use-head-safe)
- [useScript](https://unhead.unjs.io/docs/typescript/head/api/composables/use-script)

### Schema.org

- [Getting Started](https://unhead.unjs.io/docs/typescript/schema-org/guides/get-started/overview)
- [useSchemaOrg](https://unhead.unjs.io/docs/typescript/schema-org/api/composables/use-schema-org)
- [Nodes](https://unhead.unjs.io/docs/typescript/schema-org/guides/core-concepts/nodes)
- [Recipes](https://unhead.unjs.io/docs/typescript/schema-org/guides/recipes/identity)

### Guides

- [Titles](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/titles)
- [Streaming SSR](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/streaming)
- [DOM Events](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/dom-event-handling)
- [Plugins](https://unhead.unjs.io/docs/typescript/head/guides/plugins/template-params)

### Tools

- [Meta Tag Generator](https://unhead.unjs.io/tools/meta-tag-generator)
- [OG Image Generator](https://unhead.unjs.io/tools/og-image-generator)
- [Schema.org Generator](https://unhead.unjs.io/tools/schema-generator)
- [Capo.js Analyzer](https://unhead.unjs.io/tools/capo-analyzer)

### Articles

- [What is Capo.js?](https://unhead.unjs.io/learn/guides/what-is-capo)

### Research

- [State of <head> in 2026](https://unhead.unjs.io/learn/research/state-of-head-2026)
- [Streaming Head Performance](https://unhead.unjs.io/learn/research/streaming-head-performance)
- [Capo.js Performance Research](https://unhead.unjs.io/learn/research/capo-performance-research)

Copyright © 2025-2026 Harlan Wilton - [MIT License](https://github.com/unjs/unhead/blob/main/license)