---
title: Nuxt module
description: What the c15t Nuxt module registers, where each option goes between
  nuxt.config.ts and app.config.ts, and every module option.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Register the module

Add `c15t/vue` to `modules` in `nuxt.config.ts` and configure it under the
`c15t` key:

```ts title="nuxt.config.ts"
export default defineNuxtConfig({
	compatibilityDate: '2026-07-04',
	modules: ['c15t/vue'],
});
```

`c15t/vue` is the Nuxt module. You do not install a separate Nuxt package or
call `app.use()`. The [quickstart](/docs/frameworks/nuxt/quickstart) covers
the whole setup.

## What the module registers

|What|Details|
|--|--|
|Components|`ConsentRoot`, `ConsentWidget`, `ConsentDialogLink`, `ConsentDialogTrigger` and `ConsentGate`, all global. See [components](/docs/frameworks/nuxt/components).|
|Composables|Every composable `c15t/vue/vue-plugin` exports, auto-imported. See [composables](/docs/frameworks/nuxt/composables).|
|A Nuxt plugin|Creates the consent runtime for each request on the server and once in the browser, resolves the visitor's policy during server rendering, and writes the `tokens` option into the page head.|
|A server route|In `manifest()` mode, the default, one catch-all at `${routePrefix}/**` that answers `GET /api/c15t/init` and `GET /api/c15t/manifest`. `routePrefix: false`, `hosted()` and `offline()` add none.|
|The `#c15t/composables` alias|Points at the module's composables, for the few that are not auto-imported.|

In the browser, the plugin starts the consent modules once the app has
mounted. A page with `ssr: false` has no server HTML to hydrate, so there the
plugin starts them before the mount, and the policy request runs while the
app mounts. With `manifest({ resolve: 'browser' })` the resolver starts
loading as soon as the plugin runs, and the modules still start after the
mount. Starting reads the stored choice, applies Global Privacy Control,
and starts the script loader, clear-on-revocation, the network blocker, the
iframe blocker and, with `iab` set and an IAB policy, the IAB module.

With `hosted()`, the module also writes a small inline script into the head
of each `ssr: false` page. It calls your backend's `/init` while the
browser parses the HTML, and the plugin uses that response instead of sending
its own request.

## nuxt.config.ts or app.config.ts

The module merges the `c15t` key of `app/app.config.ts` over its options in
`nuxt.config.ts`. Arrays from both files are combined. The module types both
keys, including when you register it as `c15t/vue`.

|Put it in|Options|
|--|--|
|`nuxt.config.ts`|Plain data: `backendURL`, `tokens`, `presentation`, `components`, `legalLinks`, trigger options, network blocker `rules`. Also the options that only work here: `mode`, `routePrefix`, `onBuildError`, `initPrefetch` and `devtools`.|
|`app/app.config.ts`|Anything with a function: `scripts`, `callbacks`, `networkBlocker.onRequestBlocked`.|

Module options reach the browser as JSON through `runtimeConfig.public`,
which drops functions. `app.config.ts` is part of the browser bundle, so it
keeps functions.

App config can change while the page runs, through `updateAppConfig()` or hot
reload. The browser applies a change to `scripts`, `networkBlocker`,
`iframeBlocker`, `vendors`, `consentCategories`, `callbacks` and
`reloadOnConsentRevoked`. Storage, backend and experiment options are read
once when the page loads. `mode` and `routePrefix` never change at runtime.

## Change the backend URL at runtime

Nuxt applies `NUXT_PUBLIC_*` environment variables over
`runtimeConfig.public` when the server starts. To point a built app at another
backend without rebuilding it, set `NUXT_PUBLIC_C15T_BACKEND_URL`:

```sh
NUXT_PUBLIC_C15T_BACKEND_URL=https://your-project.inth.app \
  node .output/server/index.mjs
```

`NUXT_PUBLIC_INTH_PROJECT_URL` works the same way when the build read its
URL from that variable or had none. It never replaces a `backendURL` from
`nuxt.config.ts` or a `NUXT_PUBLIC_C15T_BACKEND_URL`.

The browser, server rendering and the consent route all use this value. Keep
`backendURL` in `nuxt.config.ts`: a value in `app.config.ts` is bundled with
the app and takes precedence over runtime config.

When the build bundles a manifest, the policy comes from the build. The
consent route serves the bundled snapshot and never downloads one, so the
variable only changes where consent saves and session reports go. To use
another project's policy, set the URL when you build and build again, or use
`manifest({ source: 'runtime' })`.

The consent route can reach the backend at an address the browser does not
use, such as an internal hostname. Set `NUXT_C15T_BACKEND_URL` for that. Only
the consent route reads it, and only when it downloads the manifest at
runtime or falls back to the backend's `/init`.

`nuxt generate` writes the config into the static files, so a static site
picks up a new URL only when you generate it again.

## Options

### Backend and rendering

|Option|Default|What it does|
|--|--|--|
|`backendURL`|`NUXT_PUBLIC_C15T_BACKEND_URL`, then `NUXT_PUBLIC_INTH_PROJECT_URL`|Your Inth or self-hosted backend. The build downloads the manifest from it and consent saves go to it. The module reads the variable when the build starts, and Nuxt applies it again at runtime.|
|`mode`|`manifest()`|Where the visitor's policy comes from. Import `manifest()`, `hosted()` or `offline()` from `c15t/vue`. `nuxt.config.ts` only: the build picks the snapshots and the browser code from its `type` and `resolve`, so a `runtimeConfig.public.c15t.mode` is ignored, and so is a runtime `NUXT_PUBLIC_C15T_MODE_TYPE` or `NUXT_PUBLIC_C15T_MODE_RESOLVE`, with a warning. Its `manifestURL` and `backendURL` can still change at runtime, through `NUXT_PUBLIC_C15T_MODE_MANIFEST_URL` and `NUXT_PUBLIC_C15T_MODE_BACKEND_URL`. See [the modes](#modes).|
|`routePrefix`|`'/api/c15t'`|Path of the consent route the module adds in `manifest()` mode. It answers `${routePrefix}/init` and `${routePrefix}/manifest`. `false` adds no route: the server render resolves from the snapshot directly, and the browser asks the backend's `/init`. `'/'` stops the build with `` @c15t/vue: `routePrefix` can't be '/': a consent route at the site root would catch every page. Use a path such as '/api/c15t'. `` `nuxt.config.ts` only: the route is mounted at build time, so a `runtimeConfig.public.c15t.routePrefix` or a runtime `NUXT_PUBLIC_C15T_ROUTE_PREFIX` is ignored, with a warning.|
|`onBuildError`|Unset|What a failed build-time manifest download does in `manifest()` mode. Unset, `nuxt build` stops and `nuxt dev` logs a warning, and the server fetches the policy at runtime. `'fail'` stops both. `'runtime'` lets both continue. A missing backend URL stops both in `manifest()` mode, with or without a snapshot, because the browser saves consent there. The `C15T_ON_BUILD_ERROR` environment variable overrides it. The download waits at most 10 seconds.|
|`timeoutMs`|`500`|Longest wait for the policy during server rendering. `false` or `Infinity` waits as long as the backend takes; any other value that is not a finite, non-negative number uses the default.|
|`initPrefetch`|`true`|Pages with `ssr: false` in `hosted()` mode start `/init` from an inline script in the page head, before the app's JavaScript loads. Skipped when `consentSource`, `experiment` or `hosted({ headers })` is set, and when Nitro auto-imports are off, as under `future.compatibilityVersion: 5`, because the server cannot then read `app.config.ts`. `false` turns it off. To turn it off for some routes, set the route rule `c15t: { initPrefetch: false }`. See [Content Security Policy](/docs/frameworks/nuxt/content-security-policy#allow-the-early-init-script).|
|`devtools`|`true`|Adds a c15t tab to Nuxt DevTools in development. Production builds don't include it. See [DevTools](/docs/frameworks/nuxt/components/dev-tools).|
|`reportSessions`|`true`|`manifest()` mode only. The server reports each visitor it resolves from the manifest to the backend, so Inth counts visitors it never served `/init` to. Needs an absolute `backendURL`.|

### Modes

|Mode|Where the policy comes from|
|--|--|
|`manifest()`|The default. The build downloads the manifest and the server resolves each visitor from it. The browser bundle holds no snapshot or resolver.|
|`manifest({ source: 'runtime' })`|The server fetches the manifest at runtime, caches it and refreshes it in the background. Policy edits apply without a rebuild.|
|`manifest({ snapshot })`|A manifest you supply, for example imported from a JSON file in `nuxt.config.ts`. The build downloads nothing. Consent saves still go to `backendURL`, so it needs one.|
|`manifest({ resolve: 'browser' })`|The browser resolves the policy from the manifest, for `nuxt generate` and static hosting. Only this mode adds the snapshot to the browser bundle. Takes `geoURL` and `inputs` for the visitor's location. Pair it with `routePrefix: false` on a static host.|
|`hosted()`|Every server render, and the browser on pages the server did not resolve, asks the backend's `/init`. `hosted({ backendURL, headers })` overrides the backend URL and adds headers to `/init`.|
|`offline()`|The browser resolves policy rules with no backend. `offline({ policyRules })` replaces c15t's recommended rules. Not recommended for production environments.|

`manifest()` also takes `manifestURL`, the URL the build and the server
download the manifest from, by default `${backendURL}/manifest`.
[Rendering and deployment](/docs/frameworks/nuxt/rendering) explains which
mode fits which deployment.

### Consent and storage

|Option|Default|What it does|
|--|--|--|
|`consentCategories`|The policy's categories|Limits the categories the dialog and widget show, within the policy's scope.|
|`vendors`|Unset|Vendors listed under their category with their own switch. See [vendor consent](/docs/frameworks/nuxt/vendor-consent).|
|`storageConfig`|`{ storageKey: 'c15t' }`|Cookie and localStorage name, `crossSubdomain`, `defaultDomain` and `defaultExpiryDays`. Use the same value in both config files.|
|`reloadOnConsentRevoked`|`true`|Reloads the page when a save withdraws a granted category or vendor.|
|`clearOnRevocation`|Unset|Deletes first-party cookies and storage keys for a withdrawn category. See [clear on revocation](/docs/frameworks/nuxt/clear-on-revocation).|
|`iab`|Off|Turns IAB TCF on; `{}` is enough. Also takes publisher settings, such as `publisherRestrictions`; fields left out come from the policy. Unset, `false` or `{ enabled: false }` leaves IAB off, and an `iab` policy with a vendor list throws an `IABUnavailableError`. Set it here, not only in `app.config.ts`, so pages prefetch the IAB banner. See [IAB TCF](/docs/frameworks/nuxt/iab).|

### Scripts, blocking and events

|Option|Default|What it does|
|--|--|--|
|`scripts`|Unset|Vendor scripts that load by consent category. `app.config.ts` only. See [scripts](/docs/frameworks/nuxt/scripts).|
|`networkBlocker`|Off|Rules that hold `fetch` and XHR requests until their category is allowed. See [network blocker](/docs/frameworks/nuxt/network-blocker).|
|`iframeBlocker`|On|Gates iframes that carry `data-src` and `data-category`. `false` turns it off. See [embeds](/docs/frameworks/nuxt/embeds).|
|`nonce`|Unset|A nonce for every script the loader creates and for the theme token style tag. See [Content Security Policy](/docs/frameworks/nuxt/content-security-policy).|
|`callbacks`|Unset|`app.config.ts` only. See [callbacks](/docs/frameworks/nuxt/callbacks).|

### Appearance

|Option|Default|What it does|
|--|--|--|
|`tokens`|The default theme|CSS variables the module writes into a `<style id="c15t-css-vars">` tag in the page head on every page. See [customize](/docs/frameworks/nuxt/customize#set-theme-tokens).|
|`presentation`|Policy defaults|Banner and dialog variant, position, action layout and blocking.|
|`components`|Unset|Attributes, such as `class`, for named parts of each component.|
|`legalLinks`, `bannerLegalLinks`, `dialogLegalLinks`|None shown|Links to your privacy policy and similar pages, and which surfaces show them.|
|`hideBranding` and its per-surface forms|`false`|Hide the "Secured by" tag.|
|`showTrigger` and the `trigger*` options|`false`|The floating trigger. See [ConsentDialogTrigger](/docs/frameworks/nuxt/components/consent-dialog-trigger).|
|`disableAnimation`|`false`|Turns off banner and dialog transitions.|
|`models`, `bannerModels`, `iabBannerModels`, `iabDialogModels`|Every model|Limit which policy models show a surface.|

## Verify

Run `nuxt dev` and request `/api/c15t/init` in the default `manifest()` mode.
It returns JSON with the resolved policy for your location. View the source of a page under a policy that asks for consent and
find `data-testid="consent-banner-root"`.
