---
title: v3.0.0 - The Open Standard for Cookie Banners
version: 3.0.0
date: 2026-10-08
description: Major release notes for c15t v3. One consent engine for Next.js,
  TanStack Start, React, Nuxt, Vue, Astro, Svelte, SvelteKit and plain HTML,
  banners in the server HTML, the consent manifest, policy rules, GPP, publisher
  restrictions, new integrations and a rewritten self-hosted backend.
group: changelog
tags:
  - release
  - react
  - nextjs
  - tanstack-start
  - vue
  - svelte
  - astro
  - backend
  - iab
  - integrations
  - performance
type: release
breaking: true
authors:
  - KayleeWilliams
lastModified: "2026-10-12T13:43:02+01:00"
lastAuthor: Kaylee
---
c15t v3 is a rebuild. v2 was a React library with a Next.js adapter. v3 is one
consent engine with adapters for eight frameworks and a script tag, and every
adapter passes the same conformance suite, so a policy behaves the same in
Nuxt as it does in Next.js. Server-rendered apps can now decide consent on the
server and send the banner with the page.

Your visitors shouldn't notice the upgrade, because v3 reads the consent v2
stored. Your code will notice. Read [Upgrading](#upgrading) before you start.

## Highlights

* **Eight frameworks and a script tag.** TanStack Start, Nuxt, Vue, Astro,
  Svelte and SvelteKit join Next.js and React. `@c15t/browser` covers plain
  HTML, WordPress, Shopify and other CMS sites.
* **One package.** `npm install c15t`, then import from `c15t/next`,
  `c15t/react`, `c15t/vue`, `c15t/tanstack-start` or `c15t/astro`.
* **Banner in the first HTML.** Server-rendered frameworks can render the
  banner on the server. v2 always mounted it after hydration. On
  [cookiebench](#banner-visibility-on-cookiebench), the c15t banner showed in
  27 ms on desktop. The median of the 10 tested tools was 471 ms.
* **Consent manifest.** Your server decides each visitor's policy from a
  manifest that your build downloads or your server caches. With 150 ms of
  backend latency, time to first byte in our Next.js benchmark fell from
  157.6 ms to 7.4 ms.
* **Less work in the browser.** Smaller render-blocking CSS, themes built on
  the server, the dialog and optional modules loaded on demand, and React
  components that re-render only when the value they read changes.
* **Policy rules.** A simpler policy format, 34 presets instead of 6,
  notice-only prompts, and Global Privacy Control applied live instead of
  stored as a refusal.
* **Per-vendor consent.** Visitors can switch off one vendor inside a category
  they allowed, without IAB.
* **GPP and publisher restrictions.** GPP 1.1 with 16 US state sections, and
  IAB publisher restrictions encoded in the TC string.
* **New integrations.** Cloudflare Zaraz, Klaviyo, Sentry, Vercel Speed
  Insights and the OpenAI pixel for ChatGPT Ads. `@c15t/scripts` is now
  `@c15t/integrations`.
* **Rewritten self-hosted backend.** Built on Effect SQL, with PostgreSQL,
  MySQL and SQLite, and a migrator that adopts your v2 database.

## One engine, more frameworks

v2 kept consent in a Zustand store that set up blockers and wrote to `window`
as soon as you created it. v3's consent kernel does nothing until you call a
command, so it is safe to import on the server, and every adapter reads it
the same way. One conformance suite tests React, Vue and Svelte against the
same policy, storage, event and accessibility checks, including records
written by v2.

In v2, the `c15t` package was the headless engine. In v3 it installs the
engine and the adapters, and the root `c15t` import is still the engine. The
scoped packages, such as `@c15t/nextjs`, are still published. Svelte, the
script tag and the vendor helpers install separately as `@c15t/svelte`,
`@c15t/browser` and `@c15t/integrations`.

|Framework|What's new|Start here|
|--|--|--|
|Next.js|Server-side consent in both the App Router and, new in v3, the Pages Router. Works with static export, ISR and Cache Components. `withConsentManifest()` reads your `c15t.config.ts`, so you write no client wrapper. Needs Next.js 15 or 16.|[Quickstart](/docs/frameworks/next/quickstart)|
|TanStack Start|New adapter. Consent resolves in the root route loader.|[Quickstart](/docs/frameworks/tanstack-start/quickstart)|
|React|Client-rendered apps such as Vite, React Router and Remix. Needs React 18 or 19.|[Quickstart](/docs/frameworks/react/quickstart)|
|Nuxt|New module, with server rendering and a Nuxt DevTools tab. Works with Nuxt 3 and 4, including Vue Vapor pages in Nuxt 4.6.|[Quickstart](/docs/frameworks/nuxt/quickstart)|
|Vue|New plugin for Vue 3 apps without Nuxt.|[Quickstart](/docs/frameworks/vue/quickstart)|
|Astro|New integration. The banner is plain `.astro` markup with no framework JavaScript. The dialog is an island in Svelte, React or Vue.|[Quickstart](/docs/frameworks/astro/quickstart)|
|Svelte and SvelteKit|New package, `@c15t/svelte`, with SvelteKit helpers in `@c15t/svelte/kit`.|[Svelte](/docs/frameworks/svelte/quickstart), [SvelteKit](/docs/frameworks/sveltekit/quickstart)|
|HTML and CMS sites|New package, `@c15t/browser`, with setup steps for WordPress, Webflow, Shopify and seven other platforms.|[Quickstart](/docs/frameworks/html/quickstart)|
|JavaScript|`createConsentRuntime()` for your own UI, or `@c15t/browser` for the stock banner.|[Quickstart](/docs/frameworks/javascript/quickstart)|

The script tag needs no build step. Your Inth project serves it with your
backend URL and policy already inside:

```html
<script src="https://your-project.inth.app/c15t.js" defer></script>
```

A self-hosted backend serves the same file at its own `/c15t.js`. jsDelivr
serves it too, with a `data-backend-url` attribute. Sites without a backend
use `c15t.offline.js`.

## Faster pages

Most of the speed in v3 comes from deciding consent earlier, on the server
where possible, and from keeping CSS and optional code off the critical path.

### The banner in the first HTML

Next.js, TanStack Start, Nuxt, Astro and SvelteKit can resolve consent while
they render, so the banner arrives with the page and a returning visitor's
gated scripts start at hydration. A slow backend can't hold the page past a
500 ms budget by default. The Next.js App Router streams consent by default.
The page renders without a wait. The banner follows in a later chunk of the
same response, before hydration. Set `streamBanner: false` to mount it
after hydration. [Next.js rendering](/docs/frameworks/next/rendering) compares
the options.

### Banner visibility on cookiebench

[Cookiebench](https://cookiebench.com) measures when a cookie banner becomes
visible on a Next.js page. It compares 10 consent tools and records the median
of 15 visits per tool. Inth, the team behind c15t, maintains cookiebench, and
its [methodology](https://cookiebench.com/methodology) is public.

|Profile|c15t|Next fastest tool|Median of 10 tools|
|--|--:|--:|--:|
|Desktop|27 ms|385 ms|471 ms|
|Mobile, 4× CPU slowdown, 200 KB/s|240 ms|2.15 s|2.71 s|

c15t showed its banner first on both profiles, and it added 0 ms of Total
Blocking Time. These results come from cookiebench v3.2.4 and c15t
3.0.0-alpha.11. The c15t run used a different machine from the other 9 tools.

### Consent manifest

The backend has a new `GET /manifest` endpoint. It returns one public document
per project with everything `/init` needs and no visitor data, so a CDN can
cache it. Your server resolves each visitor from this manifest and the
request's country, region, language and GPC headers. Page renders stop
waiting on the consent backend.

The recommended setup downloads the manifest when you build the app. Build
plugins for Next.js, TanStack Start, Astro, Nuxt and Vite fetch it from your
backend. The snapshot stays fixed until the next build, and a failed fetch
stops the build. For policy changes that must apply without a rebuild, your
server can fetch and cache the manifest at runtime instead. If the server
can't read it, the server falls back to `/init`.

In a Next.js 16 production build with a local backend that adds 150 ms to
each request, medians of 10 runs:

|Server render|Time to first byte|Banner painted|
|--|--:|--:|
|Backend `/init` on every request|157.6 ms|180 ms|
|Manifest, warm cache|7.4 ms|28 ms|

Both rows are v3. v2 never put the banner in the server HTML, so it never paid
for that round trip. [Data fetching](/docs/concepts/data-fetching) compares
the modes, and each framework's rendering guide shows how to turn the
manifest on.

### Less work in the browser

* **CSS.** The main stylesheet holds only what a first paint can show, and
  dialog styles load with the dialog. In a Next.js 16 build, render-blocking
  CSS from c15t fell from 10.3 KB to 8.8 KB gzip.
* **Themes.** `ConsentTheme` and `generateThemeCSS()` render theme CSS on the
  server. v2 sent every visitor about 1.9 KB gzip of generator code.
* **React re-renders.** Components re-render only when the value they read
  changes. With 10 components reading 5 categories, changing one category
  caused 20 renders in v2 and 2 in v3.
* **On-demand code.** The script loader, network and iframe blockers, IAB, GPP
  and the preference dialog load only on pages that use them.
* **Translations.** Each language has its own entry, such as
  `@c15t/translations/de`. A page loads only the languages it shows.
* **IAB pages.** Server integrations send a reference to the Global Vendor
  List instead of the list. With 1,211 vendors, the init JSON drops from 860 KB
  to 6.6 KB.

## Policies

Policy packs are now policy rules. A rule describes behavior only: who sees a
prompt, what kind, and which categories it covers. Layout moves to the
client's `presentation` option. Rules with unknown keys or categories now fail
validation.

* **Presets.** `policyPackPresets` is now `policyRulePresets`, and
  `worldNoBanner()` is now `worldOptOutNoPrompt()`. 28 presets are new, covering the US
  privacy states, Canada, Australia, Japan, Switzerland, the UK, Brazil,
  India and much of Asia and the Middle East. Each lists its sources in
  `review`. A preset is a starting point, not legal advice.
  `recommendedPolicyRules()` gives you a ready set. The
  `policy-packs-to-policy-rules` codemod renames both.
* **Notice prompts.** `prompt: 'notice'` asks for an acknowledgement instead
  of a choice, and leaves earlier refusals in place.
* **No jurisdiction label.** v2 picked `GDPR`, `CCPA` or `NONE` from a fixed
  country table. v3 decides from your rules alone.
* **Consent records.** A record stores only what the visitor did. c15t works
  out permissions against the current rule each time it reads it, so the same
  record can mean different things under opt-in and opt-out rules.
* **Asking again.** c15t asks again when a choice expires. It also asks again
  when a change to the rule affects that choice. Bump `copyRevision` to ask
  everyone after a wording change.
* **Global Privacy Control.** c15t reads GPC live on every evaluation and
  never stores it as a refusal. The restriction ends when the browser stops
  sending it.

[Policies](/docs/concepts/policies) and
[how consent works](/docs/concepts/how-consent-works) cover the model.

## Consent controls

* **Per-vendor consent outside IAB.** Declare `vendors`, and the preference
  center lists each category's vendors with its own switch. Integration
  helpers declare their own vendor.
* **`ConsentGate`** replaces `Frame` in every framework. It holds back an
  embed until the visitor allows its category or vendor.
* **Banner presentation.** `presentation` picks a `floating`, `bar`, `widget`
  or `wall` banner, its position and its actions. Clicks on the empty space
  around a non-blocking banner reach the page below it.
* **Experiments.** A/B test presentation and theme, with a per-arm summary
  from the backend. See [banner experiments](/docs/guides/banner-experiments).
* **Clear on revocation.** `clearOnRevocation` deletes declared cookies and
  storage keys when a visitor revokes a category.
* **Follow another CMP.** `consentSource` mirrors an existing CMP's decisions.

## IAB TCF and GPP

* **Global Privacy Platform.** c15t can install the GPP 1.1 API at `__gpp`.
  IAB rules add the TC string, and US rules add the visitor's state section
  for 16 states, with the US National section for everyone else.
* **Publisher restrictions.** v2 never encoded them. v3 writes them into the
  TC string, applies them to gated scripts and shows each vendor under the
  legal basis they leave.
* **IAB in every framework.** v2 had IAB banners for React and Next.js only.
  v3 adds Nuxt, Vue, Astro, Svelte, SvelteKit and the script tag.

TCF 2.4 support arrived in v2.3.0 and carries over.

## Integrations

`@c15t/scripts` is now `@c15t/integrations`. Subpaths and helper names stay the
same. All 37 v2 helpers carry over, and 5 are new, for 42 in total.
`@c15t/scripts` stays published as a deprecated re-export until v4.

|New integration|What it does|
|--|--|
|[Cloudflare Zaraz](/docs/integrations/cloudflare-zaraz)|Maps c15t categories to Zaraz purposes. Zaraz still runs the tools.|
|[Klaviyo](/docs/integrations/klaviyo)|Signup forms and onsite tracking, with a forms-only mode that keeps tracking off.|
|[Sentry](/docs/integrations/sentry)|Error reports without user data until the visitor allows measurement. Session Replay waits for that consent too.|
|[Vercel Speed Insights](/docs/integrations/vercel-speed-insights)|Loads the Web Vitals collector after measurement consent and drops its reports after a withdrawal.|
|[OpenAI pixel](/docs/integrations/openai-pixel)|The ChatGPT Ads measurement pixel, with typed conversion events.|

`createEventDispatcher()` sends one event to every allowed integration that
has an event API. Each helper now lists its vendor in the preference dialog,
with its own switch.

* **Google.** `gtag()` and `googleTagManager()` take `loadMode: 'after-consent'`. With it, the browser sends Google nothing until the visitor
  allows the category.
* **PostHog.** The `features` option turns off surveys, heatmaps, dead clicks,
  Web Vitals and feature flags. A module that you turn off never loads.
* **Mixpanel.** Visitors start opted out. Before this change, Mixpanel could
  store events and a visitor ID before the first choice.
* **Fixes.** Helpers re-run their consent steps only when their own vendor's
  consent changes. A helper without its vendor ID logs a warning and loads
  nothing. Matomo, Amplitude, Heap, Umami, Meta, X, TikTok and Crisp have
  fixes.

Segment takes new options too. See the
[integrations overview](/docs/integrations/overview).

## Self-hosted backend

`@c15t/backend` is rewritten on Effect 4 and Effect SQL, and
`c15tInstance(options).handler(request)` works as before. Queries use joins
and new indexes. In our benchmark, with PGlite and 1,000 subjects among 20,000
other rows, the subject read path went from 11.9 ms to 3.3 ms median.

* PostgreSQL, MySQL and SQLite replace the Drizzle, Prisma, TypeORM, Kysely
  and MongoDB adapters. MongoDB has no migration path.
* `c15t self-host migrate` adopts a v2 schema without dropping tables or
  columns.
* New routes serve the manifest, session reports for visitor counts,
  experiment summaries and the script-tag bundles.
* The backend accepts a save retried up to 7 days after a failure, if the
  policy did not change since the click.
* Lookups by your own user ID count only verified identity links. An API key
  or a signed `identityToken` verifies a link. Links from v2 stay unverified
  until you link them again.
* A v3 backend can share a database with a v2 backend while you roll out.

See the [backend quickstart](/docs/self-host/quickstart) and
[database setup](/docs/self-host/guides/database-setup).

`@c15t/node-sdk` has a new client, `createC15tClient()`. Methods return a
result instead of throwing, and transient failures retry. See the
[Node.js SDK reference](/docs/self-host/api/node-sdk).

## CLI and dev tools

* `c15t setup` scaffolds every supported framework, and `--plan` previews the
  changes first. It writes the same files that the quickstarts show. See
  [setup](/docs/cli/commands/setup).
* `c15t setup --codex` gives a coding agent the rules to install c15t, upgrade
  from v2 or replace another banner. The agent then checks first visit,
  reject, accept and withdrawal in a browser.
* [Codemods](/docs/cli/commands/codemods) migrate most v2 source: the
  provider and its transports, moved exports, `useConsentManager()`,
  callbacks, policy presets, CSS variables, the Tailwind CSS 3 plugin, dev
  tools, `@c15t/scripts` imports, the Node.js SDK and the backend config.
  Where a change needs a decision, they leave a `TODO(c15t v3)` comment that
  fails the build.
* Dev tools no longer depend on React. There is a panel for any framework,
  plus TanStack Devtools and Nuxt DevTools tabs. The consent trigger shows a
  DevTools button, so the two launchers no longer overlap.

## Upgrading

Each v2 package has a step-by-step guide that opens with a prompt you can
paste into a coding agent: [Next.js](/docs/frameworks/next/upgrade-v3),
[React](/docs/frameworks/react/upgrade-v3) and
[JavaScript](/docs/frameworks/javascript/upgrade-v3). If you run
your own c15t backend, [upgrade it](/docs/self-host/upgrade-v3) and the
Node.js SDK in the same release.

**Visitors keep their choices.** v3 reads the `c15t` cookie and storage key
v2 wrote. A v2 denial keeps blocking its category, and a v2 grant keeps
applying until it expires. When the rule comes from a preset and the v2 record
noted its policy, v3 also asks again if that policy changed.

**Upgrade clients and backend together.** A v3 client can't read a v2
backend's `/init`. It shows no banner and keeps optional categories denied.
Inth-hosted URLs work with v3 clients.

**Start with the codemods.**

```bash
npx @c15t/cli codemods packages-to-c15t consent-provider-options root-exports-to-subpaths use-consent-manager-to-hooks scripts-to-integrations dev-tools-to-c15t policy-packs-to-policy-rules callbacks-to-v3 theme-to-consent-theme iab-option-to-iab-provider css-variables-to-v3 postcss-tailwind3 --dry-run --json
```

Review the proposed files, then run the command again without `--dry-run`.
Self-hosted backends and Node.js servers add `backend-config-to-v3` and
`node-sdk-to-v3`.

## Breaking changes

The upgrade guides cover each of these with before and after code.

* **Every package.** ESM only. React 18 or later and Next.js 15 or later.
* **Provider.** `ConsentManagerProvider` is now `ConsentProvider`, and `mode`
  and `backendURL` merge into one value, such as `hosted({ backendURL })`. Next.js
  server rendering moves from `fetchInitialData()` to `resolveConsent()` and
  `ConsentRoot`. Codemod: `consent-provider-options`.
* **Hooks.** `useConsentManager()` is gone, replaced by one hook per field.
  Codemod: `use-consent-manager-to-hooks`.
* **Exports.** Headless hooks, trigger atoms, token types and flat banner
  parts leave the `c15t/react` and `c15t/next` roots for subpaths. Codemod:
  `root-exports-to-subpaths`.
* **Callbacks.** `onConsentSet` and `onConsentChanged` become
  `onPermissionsChanged` and `onChoiceRecorded`, with new payloads. Codemod:
  `callbacks-to-v3`.
* **Themes and styles.** `theme` tokens no longer produce CSS on their own, so
  render `ConsentTheme`. Tailwind CSS 3 needs the `c15t/postcss-tailwind3`
  plugin, and v3 renames several CSS variables. Codemods:
  `theme-to-consent-theme`, `postcss-tailwind3` and `css-variables-to-v3`.
* **IAB.** The `iab` provider option becomes `IABProvider`, and `@c15t/iab`
  drops `createIABManager`. Codemod: `iab-option-to-iab-provider`.
* **Removed.** `YouTubeEmbed`, `GoogleMap`, `useConsentScript()` and
  `ConsentButton`.
* **JavaScript.** The v2 store and `getOrCreateConsentRuntime()` are gone.
* **Self-hosted backend.** `adapter` becomes `database`, `policyPacks` moves
  to `manifest.policyRules`, and the schema needs a migration. Codemod:
  `backend-config-to-v3`.
* **Node SDK.** `c15tClient()` becomes `createC15tClient()`, and
  `consents.check` needs a client with an API key. Codemod: `node-sdk-to-v3`.
* **CLI and dev tools.** Run `c15t login` again. Dev tools move to
  `c15t/react/devtools` and `c15t/next/devtools`. Codemod:
  `dev-tools-to-c15t`.

<ContributorBlock usernames={["KayleeWilliams", "BurnedChris", "dan-hale", "bennajah", "prashantbhudwal"]} title="Thank you to our contributors" />
