---
title: Sentry
description: Configure Sentry's CDN or your own SDK with consent for error
  monitoring, Session Replay and user data.
icon: sentry
group: integrations
lastModified: "2026-10-10T16:01:45+01:00"
---
## Configure Sentry

Remove any existing Sentry Loader Script or CDN tags, then add this configuration.
Errors run before consent by default; Replay and SDK data collection wait for
`measurement`.

|Package manager|Command|
|:--|:--|
|npm|`npm install @c15t/integrations@alpha`|
|pnpm|`pnpm add @c15t/integrations@alpha`|
|yarn|`yarn add @c15t/integrations@alpha`|
|bun|`bun add @c15t/integrations@alpha`|

```ts title="src/consent-scripts.ts"
import { sentry } from '@c15t/integrations/sentry';

export const scripts = [
	sentry({
		dsn: 'https://your-key@o0.ingest.sentry.io/0',
		initOptions: {
			release: 'my-app@1.0.0',
			replaysOnErrorSampleRate: 1,
			replaysSessionSampleRate: 0.1,
			tracesSampleRate: 0.1,
		},
	}),
];
```

The default SDK is 11.4.0 with subresource integrity: `bundle.min.js`, or
`bundle.tracing.min.js` when traces are sampled. `replay.min.js` loads after
consent when a Replay sample rate is above 0.

## Register the scripts

Complete your [framework quickstart](/docs/frameworks) first. Keep its Inth
endpoint, policy, styles and consent UI. Remove the vendor's original script,
SDK initializer or tag-manager entry, so the vendor loads only through c15t.

The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
quickstart already has a `scripts` array, such as the one in `c15t.config.ts`
in the Next.js guide, add the helper to that array instead of creating a
second file.
The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
point for your framework below. These are edits to that provider, not a second
provider.

**Next.js**

Add the configuration to `scripts` in `c15t.config.ts`, next to
`next.config.ts`:

```ts title="c15t.config.ts"
import { defineConsentConfig } from 'c15t/next';
import { scripts } from './src/consent-scripts';

export default defineConsentConfig({ scripts });
```

Keep the rest of your config, such as `mode` and `routePrefix`, in the
same call. `ConsentRoot` reads the config in the browser, so the layout
keeps passing only `state`. App Router, Pages Router and static export all
read the same file. See
[Next.js scripts and embeds](/docs/frameworks/next/scripts).

**TanStack Start**

Import the configuration into your root route and pass it to the existing
`ConsentRoot` as a top-level prop. Keep the loader from the
[TanStack Start quickstart](/docs/frameworks/tanstack-start/quickstart):

```tsx title="src/routes/__root.tsx"
import { scripts } from '../consent-scripts';

<ConsentRoot state={consent} scripts={scripts}>
```

Import vendor helpers in the root route module, not in a server function.
A server function's return value must be serializable, and script
configurations carry callbacks. See
[TanStack Start scripts](/docs/frameworks/tanstack-start/scripts).

**React**

Add the configuration to the existing `ConsentProvider` options, next to
`mode`:

```tsx title="src/consent.tsx"
import { scripts } from './consent-scripts';

<ConsentProvider options={{ mode, scripts }}>
```

`mode` is the `manifest()` value from the
[React quickstart](/docs/frameworks/react/quickstart). Keep the banner,
dialog and preferences link inside the provider. See
[React scripts and embeds](/docs/frameworks/react/scripts).

**Nuxt**

Register the scripts under the `c15t` key in `app/app.config.ts`. Adjust the
relative import to where you created `consent-scripts.ts`:

```ts title="app/app.config.ts"
import { scripts } from '../src/consent-scripts';

export default defineAppConfig({
  c15t: { scripts },
});
```

The Nuxt module merges this over its options in `nuxt.config.ts` and starts
one script loader in the browser after hydration, once it has applied the
visitor's stored choice and privacy signals. Keep `scripts` out of
`nuxt.config.ts`, which reaches the browser as JSON and drops the vendor
callbacks. Write the vendor IDs into `consent-scripts.ts`. See
[Nuxt scripts and embeds](/docs/frameworks/nuxt/scripts).

**Vue**

Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:

```ts title="src/main.ts"
import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
import { scripts } from './consent-scripts';

app.use(c15tVue, {
  mode: manifest(),
  scripts,
});
```

Keep your existing `mode` and other options. The plugin starts one
script loader when the app mounts, after it has applied the visitor's stored
choice. Do not also call `createScriptLoader` from a component. See
[Vue scripts and embeds](/docs/frameworks/vue/scripts).

**Astro**

Add the scripts to the client entrypoint from the
[Astro quickstart](/docs/frameworks/astro/quickstart), `src/c15t.client.ts`,
which the integration finds on its own. Keep `astro.config.mjs` as it is.
If the module already exports scripts, combine the two arrays.

```ts title="src/c15t.client.ts"
import type { C15tClientOptionsExtension } from 'c15t/astro';
import { scripts } from './consent-scripts';

export default { scripts } satisfies C15tClientOptionsExtension;
```

Vendor helpers contain callbacks, and the integration options in
`astro.config.mjs` are serialized into the page, so do not put helpers in
the integration's `scripts` option. The integration passes the client
entrypoint to the one runtime every page shares, including across
`ClientRouter` navigation.

**Svelte**

Import the scripts in the component that owns your existing provider and
pass them as a top-level prop:

```svelte title="src/App.svelte"
<script lang="ts">
  import { ConsentProvider, manifest } from '@c15t/svelte';
  import { scripts } from './consent-scripts';
</script>

<ConsentProvider mode={manifest()} {scripts}>
  <!-- Keep your application, consent UI and preferences link here. -->
</ConsentProvider>
```

Retain the styles and consent UI from the [Svelte quickstart](/docs/frameworks/svelte/quickstart).
The provider owns the loader and disposes it on unmount.

**SvelteKit**

Add the scripts to the existing `ConsentRoot` in the root layout. Keep the
handle and the layout load from the [SvelteKit quickstart](/docs/frameworks/sveltekit/quickstart).

```svelte title="src/routes/+layout.svelte"
<script lang="ts">
  import { ConsentRoot } from '@c15t/svelte';
  import { scripts } from '../consent-scripts';

  let { children, data } = $props();
</script>

<ConsentRoot state={data.consent} {scripts}>
  {@render children()}
  <!-- Keep your consent UI and preferences link here. -->
</ConsentRoot>
```

Import vendor helpers in the layout component, not in `+layout.server.ts`:
a server load cannot send functions to the browser. Prerendered, static and
SPA-mode pages use the same `scripts` prop. If you pass an externally owned
`runtime` to the provider, register scripts when creating that runtime instead.

**HTML**

The helpers in `@c15t/integrations` are ES modules that need a bundler. On a
page that loads the c15t script tag, paste the vendor's own snippet instead
and keep it inert until its category is allowed:

```html
<script type="text/plain" data-c15t-category="measurement">
  // The vendor's snippet, unchanged
</script>
```

Use the category this guide names for the vendor. c15t runs the snippet
once that category is allowed, and reloads the page when the visitor
withdraws it. Helper options on this page, such as `loadMode`, do not apply
to a pasted snippet. See [HTML scripts](/docs/frameworks/html/scripts).

**JavaScript**

Pass the scripts to `init()` from `@c15t/browser`, next to your mode:

```ts
import { init, manifest } from '@c15t/browser';
import { scripts } from './consent-scripts';

const consent = init({
  mode: manifest(),
  scripts,
});
```

Keep the mode from your quickstart. With
`createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
A kernel you create yourself needs a loader from
`c15t/modules/script-loader`. Attach one loader per kernel. See
[JavaScript scripts](/docs/frameworks/javascript/scripts).

## Use your own Sentry SDK

For `@sentry/*` 10.67.0 or later, pass your SDK's functions instead of `dsn`.
Keep Replay in a separate module so it can load after consent:

```ts title="src/sentry-replay.ts"
import { replayIntegration } from '@sentry/browser';

export const createReplay = () =>
	replayIntegration({ maskAllText: true, blockAllMedia: true });
```

In `src/consent-scripts.ts`, replace the CDN configuration with:

```ts
import { getClient, init, setUser } from '@sentry/browser';

sentry({
	getClient,
	init,
	setUser,
	initOptions: {
		dsn: 'https://your-key@o0.ingest.sentry.io/0',
		replaysSessionSampleRate: 0.1,
		replaysOnErrorSampleRate: 1,
	},
	replay: {
		load: () => import('./sentry-replay').then((m) => m.createReplay()),
	},
});
```

Passing `init` protects startup captures before other SDK integrations run.
Custom initializers must forward the supplied options to `Sentry.init`, including
after a dynamic import. Avoid initialization as an import side effect.

Use named imports from the same Sentry package throughout your app. Next.js and
Vite split dynamically imported Replay into its own chunk; esbuild and Bun keep
it in the main bundle. A main-bundle `Sentry.replayIntegration` reference through
`import * as Sentry` also prevents the split.

### If Sentry already initializes independently

c15t applies filters once it finds the client; it cannot retract earlier data.
For startup protection, disable collection and remove event users in your own
`Sentry.init` call. With SDK 10.67, omit `queues`:

```ts
// Include these options in your app's Sentry.init call.
maxBreadcrumbs: 0,
dataCollection: {
	userInfo: false,
	cookies: false,
	httpHeaders: { request: false, response: false },
	httpBodies: [],
	urlQueryParams: false,
	graphQL: { document: false, variables: false },
	genAI: { inputs: false, outputs: false },
	databaseQueryData: false,
	queues: false,
	stackFrameVariables: false,
	frameContextLines: 0,
},
beforeSend(event) {
	delete event.user;
	delete event.request;
	return event;
},
```

These settings keep collection and event users disabled after consent too.
To restore event users, check effective c15t permission in `beforeSend`. Wait
until the adapter is registered before assigning scope users or capturing logs
and metrics.

## Options

|Option|Default|Behavior|
|--|--|--|
|`dsn`|Required to load Sentry|Your project's DSN. c15t loads the SDK and calls `Sentry.init`. A blank DSN logs an error and the script does not load.|
|`initOptions`|`{}`|Passed to `Sentry.init` with c15t's consent integration first. CDN `integrations` callbacks can access `window.Sentry`.|
|`version`|`'11.4.0'`|SDK version to load. Another version loads without subresource integrity.|
|`tracing`|`true` when traces are sampled|Loads the tracing bundle and adds `browserTracingIntegration()`.|
|`replay`|Loads CDN Replay when sampled|CDN: `{ category, options }` or `false`. SDK: `{ category, load }`.|
|`getClient`|Required for your own SDK|Your SDK's `getClient`. Supports initialization after c15t starts.|
|`setUser`|None|`setUser` from your Sentry SDK. Called with `null` while user data is not allowed. Required with `pii.user`.|
|`init`|None|Your SDK's `init`, called once when `loadMode` allows. Required for SDK mode with `'after-consent'`.|
|`replay.load`|None|With your own SDK, returns a new `replayIntegration()`. Called once, the first time Replay is allowed.|
|`loadMode`|`'always'`|When error monitoring runs. See the table below.|
|`replay.category`|`'measurement'`|Consent condition for Replay. `pii.category` must also be allowed.|
|`pii.category`|`'measurement'`|Consent condition for SDK data collection and user data. `'necessary'` permits it while the Sentry vendor is enabled.|
|`pii.user`|None|Returns the user to set when user data becomes allowed.|
|`onError`|Report to an available Sentry client|Receives CDN and Replay lifecycle failures. Set it to report CDN download failures before Sentry exists.|

Replay's category is registered only with an SDK `replay.load` function or
enabled CDN Replay with a positive sample rate.

## Loading and revocation

|`loadMode`|When Sentry runs|On withdrawal without a reload|
|--|--|--|
|`'always'`|On every page, before consent|Replay stops; errors keep reaching Sentry without user data|
|`'after-consent'`|Only while `measurement` is allowed|Sentry stops sending anything|

Replay needs both `replay.category` and `pii.category`, because recordings
include page URLs with queries and fragments. Keep Replay out of `Sentry.init`:
`replaysOnErrorSampleRate` can buffer recordings before consent. On denial or
final removal, c15t stops Replay without flushing, discards queued uploads and
blocks restarts. Data already sent stays in Sentry; a later grant preserves
the original sampling decision.

While PII is denied, c15t disables supported `dataCollection` settings and IP
inference. Before sending, it removes user fields, feedback contact details,
visitor identifiers in sessions, request data and URL queries/fragments from
supported events, spans, logs, metrics and breadcrumbs, including retained
breadcrumbs. A grant restores your resolved collection settings and allowlists.
Tracing itself is not gated. Custom messages, tags, extras, attachments and
URL paths remain diagnostics, so keep sensitive values out of them.

The vendor slug is `sentry`. Turning it off denies Replay and PII, plus errors
in `'after-consent'` mode. See [vendor consent](/docs/integrations/overview#vendor-switches-and-cookie-cleanup).

### Shared clients and configuration changes

Keep callback functions stable across renders. Equal options and callbacks
reuse the SDK bundle and active recording. Changed CDN options initialize a
client with the new DSN and settings, while reusing the page's single Replay
recorder. Its original Replay options and sampling decision last until reload.

Every active configuration targeting one client must allow each feature;
removing a loader releases its restriction and preserves surviving loaders and
downloads. Final removal denies PII and stops Replay, and also disables errors
in `'after-consent'` mode. An app-initiated `Sentry.close()` stays closed after
a grant. Separate clients have separate permissions.

## Content security policy

Allow `worker-src blob:` for Replay and your ingest or tunnel endpoint in
`connect-src`. CDN mode also needs `https://browser.sentry-cdn.com` in
`script-src`. The loader or provider's `nonce` applies to both CDN bundles;
a per-script nonce overrides it.

## Verify Sentry

Test in a private window with an opt-in policy and
`replaysSessionSampleRate: 1`. Filter DevTools Network by `sentry`.

1. Load the page. `bundle.min.js` loads and errors reach Sentry, without `user`.
   No `replay.min.js` or `replay_recording` request is sent. With
   `loadMode: 'after-consent'`, nothing loads.
2. Allow measurement. Without a reload, Replay loads and `replay_recording`
   requests start.
3. Turn measurement off and save. c15t reloads the page, and no replay request
   follows.

See the [consent verification guide](/docs/guides/verify-consent) for
navigation, expiry and hosting checks.
