---
title: "useHeadSafe() · Unhead"
canonical_url: "https://unhead.unjs.io/docs/vue/head/api/composables/use-head-safe"
last_updated: "2026-08-04T12:24:13.233Z"
meta:
  description: "Filter untrusted head input through a restrictive tag and attribute allowlist with useHeadSafe()."
  "og:description": "Filter untrusted head input through a restrictive tag and attribute allowlist with useHeadSafe()."
  "og:title": "useHeadSafe() · Unhead"
---

Home

`
Unhead on GitHub

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

**Composables**

# **useHeadSafe()**

[Copy for LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/head/7.api/composables/1.use-head-safe.md)

`**useHeadSafe()**` uses the same entry lifecycle as [`**useHead()**`](https://unhead.unjs.io/docs/head/api/composables/use-head), but filters each resolved tag through a restrictive allowlist. Use it when head data comes from users, a CMS, or another source you do not fully trust.

```
import { useHeadSafe } from '@unhead/vue'

const entry = useHeadSafe({
  title: userInput.title,
  meta: [{ name: 'description', content: userInput.description }],
})
```

It returns an `**ActiveHeadEntry**` with `**patch()**` and `**dispose()**` methods.

## Allowlist

The safe-input plugin keeps only the following attributes. Valid `**data-***` attributes are also allowed, and `**id**` is allowed on tags other than `**htmlAttrs**` and `**bodyAttrs**`.

| **Input** | **Allowed attributes** |
| --- | --- |
| `**htmlAttrs**` | `**class**`, `**style**`, `**lang**`, `**dir**` |
| `**bodyAttrs**` | `**class**`, `**style**` |
| `**meta**` | `**name**`, `**property**`, `**charset**`, `**content**`, `**media**` |
| `**link**` | `**color**`, `**crossorigin**`, `**fetchpriority**`, `**href**`, `**hreflang**`, `**imagesrcset**`, `**imagesizes**`, `**integrity**`, `**media**`, `**referrerpolicy**`, `**rel**`, `**sizes**`, `**type**` |
| `**style**` | `**media**`, `**nonce**`, `**title**`, `**blocking**` |
| `**script**` | `**type**`, `**nonce**`, `**blocking**` |

Titles are retained and escaped when rendered. Inline style and `**noscript**` content are removed.

### JSON scripts

Executable scripts are rejected. A script is kept only when it has `**textContent**` and its `**type**` ends in `**json**`, such as `**application/json**` or `**application/ld+json**`.

```
useHeadSafe({
  script: [{
    type: 'application/json',
    textContent: { theme: 'dark' },
  }],
})
```

The JSON is parsed or serialized again, and prototype-related keys are removed. `**innerHTML**` is never allowed in safe mode.

### Links

A link must have a `**rel**` plus either `**href**` or `**imagesrcset**`. URLs using `**javascript:**`, `**data:**`, or `**vbscript:**` are rejected, including encoded variants.

The following high-impact link relations are blocked because they can change document identity, start speculative fetching, or attach application metadata:

```
[
  'canonical',
  'modulepreload',
  'prerender',
  'preload',
  'prefetch',
  'dns-prefetch',
  'preconnect',
  'manifest',
  'pingback',
]
```

`**http-equiv**` is not allowed on meta tags, and event-handler attributes are not allowed on any safe tag.

## API

```
function useHeadSafe(
  input?: HeadSafe,
  options?: HeadEntryOptions,
): ActiveHeadEntry<HeadSafe>
```

Unsupported tags and attributes are silently dropped. The remaining entry still participates in normal resolution, deduplication, and framework lifecycle cleanup.

## Handling untrusted data

```
import { useHeadSafe } from '@unhead/vue'

const profile = await fetchUserProfile(userId)

useHeadSafe({
  title: profile.pageTitle,
  meta: [
    { name: 'description', content: profile.pageDescription },
    ...profile.customMetaTags,
  ],
})
```

## Security notes

The allowlist reduces the attack surface, but it should be one layer in your security model:

- Validate that input is a plain object with the expected primitive value types before calling `**useHeadSafe()**`. Custom coercion methods can throw during normalization, before the allowlist runs. See the [**~~OWASP Input Validation Cheat Sheet~~**](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html).
- Validate data as close to its source as possible.
- Use a [**~~Content Security Policy~~**](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) as an additional defense layer.
- Treat `**style**` values on `**htmlAttrs**` and `**bodyAttrs**` as untrusted CSS and validate them separately if your application permits them.
- Use `**useHead()**` only after sanitizing any fields that the safe API intentionally rejects.

For ordinary, typed SEO metadata, [`**useSeoMeta()**`](https://unhead.unjs.io/docs/head/api/composables/use-seo-meta) is usually more convenient.

[~~Edit this page~~](https://github.com/unjs/unhead/edit/main/docs/head/7.api/composables/1.use-head-safe.md)

[~~Markdown For LLMs~~](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/head/7.api/composables/1.use-head-safe.md)

**Did this page help you? **

[**useHead()** Manage document head tags with useHead(). Set titles, meta tags, scripts, and styles with typed input and reactive updates.](https://unhead.unjs.io/docs/head/api/composables/use-head) [**useSeoMeta()** Add SEO meta tags with useSeoMeta(). Type-safe API for Open Graph, Twitter cards, and 100+ meta tags with automatic property/name handling.](https://unhead.unjs.io/docs/head/api/composables/use-seo-meta)

**On this page **

- [Allowlist](#allowlist)
- [API](#api)
- [Handling untrusted data](#handling-untrusted-data)
- [Security notes](#security-notes)