---
name: nuxt-schema-org
description: Add, fix, or debug Schema.org JSON-LD in a Nuxt app with the nuxt-schema-org module. Use when a task mentions structured data, rich results, JSON-LD, site identity (Organization, Person, LocalBusiness), useSchemaOrg, defineArticle, defineProduct, defineBreadcrumb, FAQ markup, or the schemaOrg frontmatter key in Nuxt Content. Gives the default graph, verified examples, and the traps that silently drop or corrupt nodes.
license: MIT
compatibility: "Requires Node.js ^22.22.3 || ^24.15.0 || >=26.0.0. Requires Nuxt ^4.6.0 || ^5.0.0. Requires Unhead ^3.4.2."
title: nuxt-schema-org
canonical_url: https://skilld.dev/gh/harlan-zw/nuxt-schema-org
last_updated: 2026-10-13T19:11:11.000Z
---

> Index of every page on this site: [llms.txt](https://skilld.dev/llms.txt)

> **Skill from skilld.dev.** Follow the user's requested action.
> If the user asked to fork this Skill, follow the fork workflow below. Do not execute the Skill instructions while copying it.
> Otherwise, follow the instructions below for this session. You do not need to install anything.
>
> Supporting files, fetch one when the Skill refers to it: [references/nuxt-content.md](https://skilld.dev/api/skills-raw/harlan-zw/nuxt-schema-org/nuxt-schema-org/references/nuxt-content.md).
>
> If the user asked to install this Skill, run `npx skilld install harlan-zw/nuxt-schema-org/nuxt-schema-org`. Install writes the Skill files into the project, so every session loads them.
>
> ## Fork workflow
>
> A fork creates an editable local Skill with its original author and licence. The request authorizes copying and local installation.
> 1. Check `./skills/nuxt-schema-org`, the project lockfile, and selected Agent targets together. If the local directory or installed Skill exists, stop. Never overwrite an existing directory or Agent target.
> 2. Read [source metadata](https://skilld.dev/api/v1/skills/harlan-zw/nuxt-schema-org/nuxt-schema-org) once. Use sourceUrl, sourceCommit, skillPath, sourceGone, and license. If the source is gone or its path is missing, stop. If license is null, read licence files at the source commit.
> 3. Fetch only the source commit into a temporary Git repository. Do not clone full history. Derive repository_url from sourceUrl, including repository renames. If sourceCommit is absent, resolve the sourceUrl ref once. Set source_commit to that actual commit. Run these commands in one shell call:
>
> ```sh
> git init --quiet "$temporary_dir"
> git -C "$temporary_dir" fetch --quiet --depth=1 "$repository_url" "$source_commit"
> git -C "$temporary_dir" checkout --quiet --detach FETCH_HEAD
> ```
>
> Read applicable licence declarations and notices at that commit. If copying is not permitted, report the restriction and stop.
> 4. Inspect source entries together, then copy the directory containing skillPath into `./skills/nuxt-schema-org`. Use the user's path if selected. Keep the original SKILL.md, relative links, scripts, binary assets, and executable modes. Exclude .git metadata. Reject symlinks and paths outside the Skill directory. After checking entries, use cp -a where available. A regular source directory needs no custom copy script. Do not save this page wrapper as SKILL.md.
> Preserve author credit, notices, and applicable licence files from repository or parent directories. Add PROVENANCE.md with the Skill page, source URL, actual commit, original path, and licence. Retain any existing PROVENANCE.md and record new provenance separately. Batch source inspection, copying, and provenance work where practical.
> 5. In the project root, run `skilld install ./skills/nuxt-schema-org --mode copy --plain`. If skilld is unavailable, use `npx skilld install ./skills/nuxt-schema-org --mode copy --plain`. This known command needs no help lookup. Install does not support --json. Use detected Agent targets, or add --agent for the targets the user selected. Install the local path, never the upstream selector. If installation fails, preserve the local copy and report the exact failure.
> 6. Confirm the local lockfile source and installed Agent copies once. Report the local path, actual commit, and Agent targets. After edits, reinstall the same local path. Upstream updates must not replace it. Do not publish or push unless the user asks.

# nuxt-schema-org

Requires Nuxt `^4.6.0 || ^5.0.0` and Unhead 3.4.2 or newer.
The module renders one Schema.org `@graph` per page as `<script type="application/ld+json">`.
It wraps [Unhead Schema.org](https://unhead.unjs.io/docs/schema-org/guides/get-started/overview) and adds site config, Nuxt Content, and i18n. Docs: https://nuxtseo.com/docs/schema-org

## Setup

Install with `npx nuxt module add schema-org`. Config key: `schemaOrg`.
Included in `@nuxtjs/seo` v6. If the app uses `@nuxtjs/seo`, configure the `schemaOrg` key and do not add the module again.

Set `site.url`. Every `@id` and relative URL resolves against it. `site.name` and `site.description` feed the `WebSite` node.
The module needs SSR. Crawlers read only the SSR response. With `ssr: false` the module warns in dev.

## Automatic behaviour

With `defaults: true` (the default), every page gets:

- `WebSite` from `site.name`, `site.description`, and the current locale.
- `WebPage` for the page canonical link, or the current URL without one. Its `name` comes from the page `<title>`.
- The identity node, if `schemaOrg.identity` is set. `WebSite.publisher` and `WebPage.about` point to it.

`WebPage` picks a subtype from the last path segment: `about`, `about-us` to `AboutPage`; `contact`, `contact-us`, `get-in-touch` to `ContactPage`; `faq` to `FAQPage`; `search` to `SearchResultsPage`; `checkout` to `CheckoutPage`.

The resolver links nodes. `Product.brand`, `Article.publisher`, and `Article.author` fall back to `#identity`. `mainEntityOfPage` points to the page `WebPage`.
Nodes with the same `@id` merge. `defineWebPage({ name: 'X' })` extends the default `WebPage`; it does not add a second one.
Enum values take the short form: `availability: 'InStock'` renders as `https://schema.org/InStock`.

Set `schemaOrg: { defaults: false }` to emit only the nodes you add.

## Site identity

Put a static identity in `nuxt.config.ts` as a plain object with `type` (`'Organization' | 'Person' | 'LocalBusiness'`).
For a LocalBusiness subtype, keep `type: 'LocalBusiness'` and add `'@type'`:

```ts
export default defineNuxtConfig({
  schemaOrg: {
    identity: {
      'type': 'LocalBusiness',
      '@type': 'Restaurant', // any https://schema.org/LocalBusiness#subtypes
      'name': 'The Coastal Kitchen',
      'address': { streetAddress: '742 Oceanview Blvd', addressLocality: 'Santa Cruz', addressCountry: 'US' },
      'openingHoursSpecification': [{ dayOfWeek: ['Monday', 'Tuesday'], opens: '11:30', closes: '22:00' }],
    },
  },
})
```

The identity `@type` renders as `["Organization", "LocalBusiness", "Restaurant"]`.

String subtypes such as `'OnlineStore'` are not valid `type` values.
For an online store, use `type: 'Organization'` with `'@type': ['Organization', 'Store', 'OnlineStore']`.

If identity depends on runtime data, call `useSchemaOrg([defineOrganization({...})])` in `app.vue`.
A `defineOrganization` or `definePerson` node in the graph becomes the `#identity` node.

## Add nodes on a page

`useSchemaOrg()` and every `defineX()` are auto-imported. Pass an array.

```vue
<script setup lang="ts">
const { data: product } = await useFetch('/api/product')

useSchemaOrg([
  defineProduct({
    name: product.value!.name,
    description: product.value!.description,
    image: product.value!.image, // relative paths resolve against site.url
    offers: [{ price: product.value!.price, priceCurrency: 'USD', availability: 'InStock' }],
  }),
])
</script>
```

Await data before `useSchemaOrg()`. In production the graph renders once on the server. Data fetched in `onMounted` never reaches crawlers.
Refs, getters, and `computed()` work for the whole array or for single fields.

FAQ: set the `WebPage` type, then add questions. They attach to `WebPage.mainEntity`.

```ts
useSchemaOrg([
  defineWebPage({ '@type': 'FAQPage' }),
  defineQuestion({ name: 'Do you ship abroad?', acceptedAnswer: 'Yes, to 30 countries.' }),
])
```

Breadcrumbs: the last `itemListElement` may omit `item`. The resolver adds `position`.

Custom node: pass a plain object with `'@type'`. Pass the items of a `@graph` array, never a full JSON-LD document.

Components exist for templates, such as `<SchemaOrgArticle />`. Prefer `useSchemaOrg()` in script code.

## Nuxt Content and i18n

Nuxt Content v3 needs a schema change before the `schemaOrg` frontmatter key renders. See [references/nuxt-content.md](https://skilld.dev/api/skills-raw/harlan-zw/nuxt-schema-org/nuxt-schema-org/references/nuxt-content.md).

With `@nuxtjs/i18n`, each locale gets its own `WebSite` node linked by `workTranslation`.
Per-locale `name` and `description` come from `nuxtSiteConfig.name` and `nuxtSiteConfig.description` in the locale messages.
The config identity is shared across locales. For translated identity fields, call `defineOrganization({ name: () => t('...') })` in `app.vue`.

## Traps

- **`defineLocalBusiness({ '@type': 'Restaurant' })` as the config identity loses the LocalBusiness resolver.** It renders `["Organization", "Restaurant"]` and leaves `openingHoursSpecification` unnormalized. Use the plain object form above.
- **Type names are case sensitive.** `FAQPage` is valid. `FaqPage` renders as written and is not a schema.org type.
- **Content v3 without a `head` field in the collection schema drops the `schemaOrg` frontmatter.** No error, no node. See [references/nuxt-content.md](https://skilld.dev/api/skills-raw/harlan-zw/nuxt-schema-org/nuxt-schema-org/references/nuxt-content.md).
- **A node without `'@type'` still renders, and search engines ignore it.** The module warns in dev only.
- **A LocalBusiness identity with `logo` adds a second node, `#organization`.** The logo goes on that node, not on `#identity`. This is intentional upstream: unhead emits it for Google Logo rich results.

## Version limits

v7 breaking changes and their fixes:

- **Nuxt 3 is not supported.** Upgrade to Nuxt `^4.6.0 || ^5.0.0`, Vue `^3.5.0`, and Node `^22.22.3 || ^24.15.0 || >=26.0.0`.
- **Unhead 2 is not supported.** Nuxt 4.6 already ships Unhead 3.4.2 or later. Remove any Unhead 2 pin.
- **Site Config 5 is required.** If the app depends on `nuxt-site-config` directly, upgrade it to v5.
- **Nuxt Content v2 is not supported.** Nuxt stops at startup with a version error. Upgrade to Nuxt Content v3.
- **The `#schema-org` runtime directory alias is gone.** Import from `#schema-org/app`, `#schema-org/server`, or `#schema-org/types`.
- **With `reactive: false`, client code calls to `useSchemaOrg()` do nothing.** The functions and components leave the client bundle. Set `schemaOrg: { reactive: true }` to update the graph on the client.
- **The DevTools panel needs `nuxtseo-devtools-host`.** Run `pnpm add -D nuxtseo-devtools-host@^1`.

The module vendors Unhead Schema.org 3 and aliases `@unhead/schema-org` to that copy.
Full node list: https://unhead.unjs.io/docs/schema-org/api/schema/organization

## Config

- `defaults` (`true`): add `WebSite`, `WebPage`, and identity.
- `reactive` (dev, or `ssr: false`): ship schema code to the client and update on navigation. SEO does not need it.
- Other options: https://nuxtseo.com/docs/schema-org/api/config

To change the meta that feeds every node, such as `host` or `url`, edit the object that the `schema-org:meta` hook receives in a Nuxt plugin. The hook may be async. A hook `url` sets `host` and `path` and wins over a canonical link.

## Debug

- `/__schema-org__/debug.json` in dev, or in production with `debug: true`.
- Nuxt DevTools has a Schema.org tab. The panel needs `nuxtseo-devtools-host` as a dev dependency. In dev the client shows a reactive graph; crawlers see only the SSR script.
