---
title: "Migrate to v3 · Unhead"
canonical_url: "https://unhead.unjs.io/docs/vue/migration-guide/v3"
last_updated: "2026-09-22T20:28:29.640Z"
meta:
  description: "Migrate from Unhead v2 to v3. Covers breaking changes, deprecated compatibility APIs, and their replacements."
  "og:description": "Migrate from Unhead v2 to v3. Covers breaking changes, deprecated compatibility APIs, and their replacements."
  "og:title": "Migrate to v3 · Unhead"
---

Home

`
Unhead on GitHub

Switch to VueSwitch to TypeScriptSwitch to ReactSwitch to SvelteSwitch to Solid.jsSwitch to AngularSwitch to Nuxt

**Migration Guide**

# **Migrate to v3**

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

Unhead v3 changes several core defaults and removes v2 compatibility behavior. This guide covers the breaking changes and the deprecated compatibility APIs that remain temporarily available.

## Automated Migration Checks

Add `**ValidatePlugin**` during the upgrade to report v2 patterns:

```
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',
  }
})
```

The [**~~unified Vite plugin~~**](https://unhead.unjs.io/docs/head/guides/build-plugins/overview) injects `**ValidatePlugin**` in development unless `**validate**` is disabled.

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

---

## `**@unhead/addons**` → `**@unhead/bundler**`

🚦 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 use the framework bundler entry:

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

- plugins: [unhead()]
+ plugins: Unhead().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

🚦 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 call remains the same after the import change. Nuxt users do not configure this plugin directly.

---

## Legacy Property Names

🚦 Impact Level: **High**

The current `**createHead()**` entry points omit `**DeprecationsPlugin**`. Rename these properties before upgrading; the deprecated plugin is still available for staged migrations.

### `**children**` → `**innerHTML**`

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

### `**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'**`

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

### Quick Reference

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

---

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

The following config options have been removed:

| **Removed Option** | **Replacement** |
| --- | --- |
| `**canonicalHost**` | `**host**` |
| `**canonicalUrl**` | `**path**` + `**host**` |
| `**position**` | `**tagPosition**` |
| `**defaultLanguage**` | `**inLanguage**` |
| `**defaultCurrency**` | `**currency**` |

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

---

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

🚦 Impact Level: **Medium**

### `**createHeadCore**` → Platform `**createHead**`

```
- import { createHeadCore } from 'unhead'
+ import { createHead } from 'unhead/client'

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

For SSR, import `**createHead**` from `**unhead/server**`. The low-level `**createUnhead()**` API now requires a renderer and is intended for adapter authors.

### `**headEntries()**` → `**entries**` Map

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

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

🚦 Impact Level: **Medium**

### `**/legacy**` Export Path Deprecated

The `**@unhead/vue/legacy**` import still works but is deprecated and scheduled for removal in v4. 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

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

---

## Server Utilities

🚦 Impact Level: **Low**

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

🚦 Impact Level: **Low**

The `**init**` hook was removed. `**dom:renderTag**` remains in the type definitions for compatibility but is deprecated and no longer called internally. `**dom:rendered**` is also deprecated but is still emitted; prefer the `**onRendered**` entry option for entry-specific work.

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

🚦 Impact Level: **Low**

| **Removed Type** | **Replacement** |
| --- | --- |
| `**Head**` | `**SerializableHead**` |
| `**ResolvedHead**` | `**SerializableHead**` |
| `**MergeHead**` | Use generics directly |
| `**MetaFlatInput**` | `**MetaFlat**` |
| `**ResolvedMetaFlat**` | `**MetaFlat**` |
| `**RuntimeMode**` | Removed (no replacement needed) |

```
- import type { Head, MetaFlatInput, RuntimeMode } from 'unhead/types'
+ import type { MetaFlat, SerializableHead } from 'unhead/types'
```

---

## 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. Use the new `**defineLink**` and `**defineScript**` helpers to declare custom values without losing strictness on known ones.

### 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 non-standard `**rel**` values not covered by `**KnownLinkRel**` (e.g., OpenID endpoints, RSD links), use `**defineLink**`:

```
import { defineLink, useHead } from 'unhead'

useHead({
  link: [
    defineLink({ rel: 'openid2.provider', href: 'https://example.com/openid' }),
  ]
})
```

### Script Tags

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

```
import { defineScript, useHead } from 'unhead'

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

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

When `**rel**` or `**type**` comes from a variable typed as `**string**`, TypeScript cannot narrow the union. Wrap it with `**defineLink**` / `**defineScript**` or use `**as const**` for literals:

```
import { defineLink, useHead } from 'unhead'

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

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

---

## Other API Changes

- `**resolveScriptKey**` : Internal utility, no longer exported
- `**setHeadInjectionHandler**` (Vue) : Head injection is handled automatically
- `**DeprecationsPlugin**` and Vue's `**resolveUnrefHeadInput**` remain available as compatibility helpers. Avoid them in new code.

---

## 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; do not depend on the compatibility plugin

// 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 { createHead } from 'unhead/client'
+ import { createHead } from 'unhead/server'

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

// Vue
- import { createHeadCore, 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, including subpath exports, explicit context, and opt-in plugins.](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 API Changes](#other-api-changes)
- [Quick Reference: Import Changes](#quick-reference-import-changes)