[Consent banner](/consent-banner)

# Add a cookie consent banner to Astro

c15t puts the banner in your Astro site as plain Astro markup. It 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**: Astro 5, 6 and 7
- **Library**: c15t, open source
- **Code checked**: 28 October 2026

## Install c15t in an Astro site

c15t is the open source consent library behind Inth Consent. The banner is plain `.astro` markup and ships no framework JavaScript. Each choice goes to your Inth project.

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

The preference dialog is an island. This setup renders it with Svelte.

For React, install `@astrojs/react react react-dom` and register `react()`. For Vue, install `@astrojs/vue vue` and register `vue()`. Use the framework your site already ships.

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

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

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

## Pick static or server output

On a static host, use `hosted()`. The browser asks your Inth backend for the policy, so you need no server.

```js
import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';
import c15t, { hosted } from 'c15t/astro';

export default defineConfig({
  integrations: [svelte(), c15t({ mode: hosted() })],
});
```

With a server adapter, `c15t()` with no mode bundles your policy into the server build. The first HTML then contains the banner, or no banner for a visitor who already chose.

```js
import node from '@astrojs/node';
import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';
import c15t from 'c15t/astro';

export default defineConfig({
  adapter: node({ mode: 'standalone' }),
  integrations: [svelte(), c15t()],
  output: 'server',
});
```

Rebuild after you change a policy. Use the adapter for your host in place of `@astrojs/node`.

## Hold scripts until consent

List the scripts in `src/c15t.client.ts`. The integration finds this file without config.

```ts
import { posthog } from '@c15t/integrations/posthog';
import type { C15tClientOptionsExtension } from 'c15t/astro';

export default {
  scripts: [
    posthog({
      id: 'phc_your_project_key',
      initOptions: { cookieless_mode: 'never' },
      loadMode: 'after-consent',
    }),
  ],
} satisfies C15tClientOptionsExtension;
```

Keep vendor helpers out of `astro.config.mjs`. The integration writes its options into the page as JSON, and `c15t()` stops with an error if an option holds a function. Remove every other loader for PostHog, so it loads once and only through c15t.

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

## Add the banner to your layout

Wrap every page in a layout with the consent components. `ConsentScript` goes in `<head>` and puts the banner’s styles in the page, so you import no stylesheet.

```astro
---
import { ClientRouter } from 'astro:transitions';
import {
  ConsentBanner,
  ConsentDialog,
  ConsentDialogLink,
  ConsentScript,
} from 'c15t/astro/components';

interface Props {
  title: string;
}

const { title } = Astro.props;
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <title>{title}</title>
    <ConsentScript />
    <ClientRouter />
  </head>
  <body>
    <slot />
    <footer>
      <ConsentDialogLink>Privacy settings</ConsentDialogLink>
    </footer>
    <ConsentBanner />
    <ConsentDialog />
  </body>
</html>
```

`ClientRouter` is optional. With it, the consent runtime stays alive across page navigation. Without it, each page load starts the runtime again from the stored choice.

## Add consent to your React, Vue or Svelte islands

With Astro islands, you can add interactive components to a static page. You can use c15t with them too.

The preference dialog is an island in Svelte, React or Vue. Your own islands can read consent and open the dialog.

Use the framework that your site already ships, so visitors do not download a second framework. For a pure Astro site, we recommend Svelte, because it adds the least JavaScript to the page. Svelte is also the default.

This React island shows the measurement permission and opens the preference dialog. The code comes from the c15t docs.

```tsx
import { getConsentClient, openDialog } from 'c15t/astro/client';
import { ConsentProvider, useConsent } from 'c15t/react';

const MeasurementStatus = () => {
  const allowed = useConsent('measurement');
  return (
    <p>
      <span data-testid="measurement-status">
        Measurement is {allowed ? 'allowed' : 'not allowed'}.
      </span>{' '}
      <button
        onClick={() => {
          void openDialog();
        }}
        type="button"
      >
        Change cookie settings
      </button>
    </p>
  );
};

const ConsentStatus = () => {
  // The page's consent runtime, which the c15t integration started.
  const runtime = getConsentClient()?.runtime;
  if (!runtime) {
    return null;
  }
  return (
    // `colorScheme: null` leaves dark mode to the integration.
    <ConsentProvider options={{ colorScheme: null }} runtime={runtime}>
      <MeasurementStatus />
    </ConsentProvider>
  );
};

export default ConsentStatus;
```

Render the island only in the browser, where the consent runtime is:

```astro
<ConsentStatus client:only="react" />
```

Give each island the runtime of the page from `getConsentClient()?.runtime`. A second runtime keeps its own copy of consent and does not stay in step with the banner. Call `openDialog()` from `c15t/astro/client` to open the preference dialog.

Svelte islands use `ConsentProvider` from `c15t/svelte`. Vue islands use the `c15tVue` plugin from `c15t/vue/vue-plugin`. [Astro islands](https://c15t.com/docs/frameworks/astro/islands) covers each one.

For the framework setup, see the [Svelte](/consent-banner/svelte), [Vue](/consent-banner/vue) and [React](/consent-banner/react) guides.

## Google Consent Mode v2 in Astro

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/c15t.client.ts`, 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. With `hosted()`, the browser asks your backend for the policy, and the backend reads the visitor’s location from that request. Regional policies need no extra code.

With server output, the server resolves each visitor from the bundled policy and the request headers instead.

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

## When the banner appears

With server output, the first HTML contains the banner. With static output, the banner appears after the browser gets the policy from your backend. In both cases, the banner ships no framework JavaScript.

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

## Check that consent gates work

Build the site, start the preview and open a private window with the Network tab open. Test under an opt in policy, such as the EU policy.

1. Under an opt in policy, no PostHog request leaves the page before a choice.
2. After Reject All and a reload, PostHog stays off.
3. When you turn on measurement in Privacy settings, PostHog loads.
4. 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), [Svelte](/consent-banner/svelte), [SvelteKit](/consent-banner/sveltekit), [HTML](/consent-banner/html) and [JavaScript](/consent-banner/javascript).

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

## Questions about Astro consent

### Does it work on a static Astro site?

Yes. Set `hosted()` as the mode. The browser asks your Inth backend for the policy, so you need no server.

### Does the banner add framework JavaScript?

No. The banner is plain `.astro` markup. Only the preference dialog is an island.

### Can the dialog use React or Vue?

Yes. Install the Astro integration for React or Vue instead of Svelte. With more than one registered, set `ui: 'react'` or `ui: 'vue'` on `c15t()`.

### Can I use React, Vue or Svelte components in Astro?

Yes. Your own React, Vue or Svelte islands can read consent from the page runtime and open the preference dialog. For a pure Astro site, we recommend Svelte, because it adds the least JavaScript to the page.

### Where do vendor scripts go?

In `src/c15t.client.ts`. Options in `astro.config.mjs` go into the page as JSON, so they cannot hold functions.

### 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 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.