[Consent banner](/consent-banner)

# Add a cookie consent banner to SvelteKit

c15t puts the banner in your SvelteKit app as code. It resolves consent on the server, holds tags until the visitor chooses and keeps Google Consent Mode v2 in sync.

[Start for free](/dashboard/sign-up) · [See the code](#install)

Ask your coding agent: `Add inth to my app: inth.com/SKILL.md`

- **Framework**: SvelteKit 2.63 and later, Svelte 5.20 and later
- **Library**: c15t, open source
- **Code checked**: 28 October 2026

## Install c15t in a SvelteKit app

c15t is the open source consent library behind Inth Consent. It renders the banner on the server and records each choice in your Inth project.

```bash
npm install c15t @c15t/integrations
```

The components come from `c15t/svelte`. The server helpers come from `c15t/svelte/kit` and the Vite plugin from `c15t/svelte/vite`.

Add your Inth project URL to `.env`. Copy it from your Inth dashboard.

```bash
PUBLIC_C15T_BACKEND_URL=https://your-project.inth.app
```

Add `consentManifest` to the plugins in `vite.config.ts`. It downloads your policy during `vite build` and keeps it on the server. Rebuild after you change a policy.

```ts
import adapter from '@sveltejs/adapter-auto';
import { sveltekit } from '@sveltejs/kit/vite';
import { consentManifest } from 'c15t/svelte/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [consentManifest(), sveltekit({ adapter: adapter() })],
});
```

On SvelteKit 2, keep the adapter in `svelte.config.js` and do not give it to `sveltekit()`.

Code on this page comes from the [c15t SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart), checked 28 October 2026.

## Add the server hook

`c15tHandle` reads the consent cookie, the location headers and Global Privacy Control one time for each request. It also writes the banner’s styles into the HTML `<head>`.

```ts
import { c15tHandle } from 'c15t/svelte/kit';

export const handle = c15tHandle();
```

Type `event.locals.c15t` with one line in `src/app.d.ts`.

```ts
/// <reference types="c15t/svelte/kit/locals" />
```

## Resolve consent in the root layout

Export `loadConsent` as the server load of the root layout. It resolves the policy for the visitor from the bundled snapshot, so a page render sends no policy request to the backend.

```ts
export { loadConsent as load } from 'c15t/svelte/kit';
```

Then render `ConsentRoot` in `src/routes/+layout.svelte` and give it `data.consent` unchanged.

```svelte
<script lang="ts">
  import { posthog } from '@c15t/integrations/posthog';
  import {
    ConsentBanner,
    ConsentDialog,
    ConsentDialogLink,
    ConsentRoot,
  } from 'c15t/svelte';

  let { children, data } = $props();

  const scripts = [
    posthog({
      id: 'phc_your_project_key',
      initOptions: { cookieless_mode: 'never' },
      loadMode: 'after-consent',
    }),
  ];
</script>

<ConsentRoot state={data.consent} {scripts}>
  {@render children()}
  <footer>
    <ConsentDialogLink>Privacy settings</ConsentDialogLink>
  </footer>
  <ConsentBanner />
  <ConsentDialog />
</ConsentRoot>
```

Create `scripts` in the layout component, not in `+layout.server.ts`. A server load cannot send functions to the browser. Keep `ConsentRoot` in the root layout, so it stays mounted across navigation.

## Hold scripts until consent

With `loadMode: 'after-consent'`, PostHog waits for measurement permission. Remove any PostHog snippet from `src/app.html`, so c15t is the only thing that loads it.

[SvelteKit scripts and embeds](https://c15t.com/docs/frameworks/sveltekit/scripts) covers more vendors, iframes and the network blocker. The [c15t integrations](https://c15t.com/docs/integrations/overview) list the helper for each vendor.

## Prerendered pages and static sites

A prerendered page goes to every visitor, so it cannot contain the consent of one visitor.

On those pages, the browser resolves the policy after hydration. Optional categories stay denied until then. Server rendered pages in the same app keep the banner in their HTML.

```ts
// Build this page once as static HTML. While SvelteKit prerenders,
// `loadConsent` returns no visitor's consent, so the browser resolves it
// here.
export const prerender = true;
```

With `adapter-static`, SvelteKit prerenders every page. Set `hosted()` as the mode in the hook, so the browser asks the backend directly.

```ts
import { c15tHandle, hosted } from 'c15t/svelte/kit';

export const handle = c15tHandle({ mode: hosted() });
```

[SvelteKit rendering](https://c15t.com/docs/frameworks/sveltekit/rendering) also covers SPA mode and edge adapters.

## Google Consent Mode v2 in SvelteKit

The Google tag helper sends the Consent Mode v2 default before your config and an update after each choice. It ships in `@c15t/integrations`.

```ts
import { gtag } from '@c15t/integrations/google-tag';

export const scripts = [gtag({ id: 'G-XXXXXXXXXX', category: 'measurement' })];
```

Add the helper to the `scripts` array in `src/routes/+layout.svelte`, next to PostHog.

Measurement maps to `analytics_storage`. Marketing maps to `ad_storage`, `ad_user_data` and `ad_personalization`.

By default the helper loads gtag.js before a choice. Google then gets a default that matches the current permissions. Set `loadMode: 'after-consent'` to send nothing to Google until the visitor accepts.

[Read the developer guide to Consent Mode v2](/blog/developer-guide-to-consent-mode-v2).

## Show the right banner in each region

Your Inth project sets a policy for each region, such as opt in for the EU and UK or opt out for California. On the server, `c15tHandle` reads the location headers of each request, and `loadConsent` applies the policy for that location.

Test each region before launch. [See how regional policies work](/consent-banner/regions).

## Keep the banner in the first HTML

The root layout resolves consent on the server before the page renders, so the banner is part of the first HTML. `loadConsent` never stops the render. If it takes longer than 500 ms, it returns the stored choice without a policy, and the browser resolves the policy after hydration.

Measure your own pages before and after you add the banner.

## Check that consent gates work

Build with `vite build`, serve the build with `vite preview` and open a private window with the Network tab open. Test under an opt in policy, such as the EU policy.

1. The first HTML contains the banner.
2. Under an opt in policy, no PostHog request leaves the page before a choice.
3. After Reject All and a reload, PostHog stays off.
4. When you turn on measurement in Privacy settings, PostHog loads.
5. When you turn measurement off, the page reloads without PostHog.

Choices post to your Inth project. Check that Inth has a record for each one. The [c15t verify consent guide](https://c15t.com/docs/guides/verify-consent) has the full release checklist.

## Use Inth in production

c15t is open source. Point it at your Inth project, which hosts the backend and keeps the consent records. [See Inth open source](/oss).

c15t also has a local mode with no backend. It keeps no consent records, so the docs advise against it in production.

Want to see what else your repo sends before consent? [Run a free codebase scan](/run-free-codebase-scan).

## Cookie consent banners for other frameworks

The same banner and the same Inth project work in other stacks. See the setup for [Next.js](/consent-banner/nextjs), [React](/consent-banner/react), [TanStack Start](/consent-banner/tanstack-start), [Nuxt](/consent-banner/nuxt), [Vue](/consent-banner/vue), [Astro](/consent-banner/astro), [Svelte](/consent-banner/svelte), [HTML](/consent-banner/html) and [JavaScript](/consent-banner/javascript).

[See how the Inth consent banner works](/consent-banner).

## Questions about SvelteKit consent

### Does it work with prerendered pages?

Yes. On a prerendered page, the browser resolves consent after hydration. Optional categories stay denied until then.

### Does it work with adapter-static?

Yes. Set `hosted()` as the mode in `c15tHandle`. The browser then asks the backend for the policy.

### Which SvelteKit versions does it support?

SvelteKit 2.63 and later, including SvelteKit 3, with Svelte 5.20 and later.

### Why do scripts go in the layout component?

A server load cannot send functions to the browser. Vendor helpers contain functions, so create them in `+layout.svelte`.

### How do I set Consent Mode v2 defaults before gtag loads?

Use the c15t Google tag helper. It sends the default before your config and an update after each choice.

### Does it show a different banner in each region?

Yes. Set a policy for each region in your Inth project. c15t applies the policy that matches the visitor’s location. [See regional policies](/consent-banner/regions).

### Can I use it in Svelte without SvelteKit?

Yes. See the [Svelte cookie consent banner](/consent-banner/svelte).

### Can I run it without a backend?

c15t has a local mode with no backend, but it keeps no consent records. Use your Inth project in production.