# Klaviyo

The c15t klaviyo helper loads Klaviyo.js only after visitors allow marketing and measurement in Inth. A forms only mode runs signup forms after marketing with tracking off.

## Key facts

- **Tool:** Klaviyo
- **Category:** Other
- **Consent categories:** Marketing, Measurement
- **Setup method:** c15t package
- **Package:** @c15t/integrations

## Links

- [Documentation](https://c15t.com/docs/integrations/klaviyo)
- [Klaviyo](https://www.klaviyo.com/)

Grow your lists with Klaviyo signup forms and keep visitor tracking under consent. By default, Klaviyo.js waits for marketing and measurement.

Inth keeps the consent record of each choice, with the visitor’s country and the policy that applied.

[Start for free](/dashboard/sign-up)

## Before you start

You need an Inth project that connects to your app and c15t in that app. You also need the six character public API key from Settings > Account > API keys in Klaviyo.

Never put a private key (pk\_...) in browser code. If you pass one, the helper logs an error, and Klaviyo does not load.

[Open the quickstart for your framework](https://c15t.com/docs/frameworks)

## How consent works with Klaviyo

One script, Klaviyo.js, serves signup forms and Active on Site tracking. Klaviyo has no setting that loads one without the other.

So by default, the c15t klaviyo helper waits for marketing and measurement. With one category or none, c15t requests nothing from Klaviyo, and Klaviyo stores nothing.

Once the second category has permission, Klaviyo.js loads once without a page reload. On withdrawal, c15t removes the loader and reloads the page.

The helper sets the vendor slug klaviyo. So a visitor can allow both categories and still switch Klaviyo off.

## Installation

The helper has two modes. Pick forms with tracking, or forms alone.

### Forms and tracking

Klaviyo.js loads after marketing and measurement. Signup forms and Active on Site tracking both run.

```ts
mode: 'full'
```

### Forms only

Klaviyo.js loads after marketing and sets the \_\_kla\_off cookie first. Forms work, and identify and track send nothing.

```ts
mode: 'forms-only'
```

Forms only is not a cookieless mode. Klaviyo.js still writes an anonymous \_\_kla\_id cookie, and form views still reach Klaviyo form analytics.

## Setup steps

#### Install the integration package

Add the helpers next to c15t. An import alone does not start Klaviyo.js.

```
pnpm add @c15t/integrations
```

#### Add your public API key

The public API key, also called the site ID, appears on every page that loads Klaviyo. So it is safe in browser code.

```
import { klaviyo } from '@c15t/integrations/klaviyo';

export const scripts = [klaviyo({ publicApiKey: 'YOUR_PUBLIC_API_KEY' })];
```

Pass your own category condition if your legal review classes Klaviyo in another way.

#### Send your own events only with consent

The helper never calls identify or track. The klaviyo object exists only after consent on this page, so optional chaining drops calls made without it.

```
window.klaviyo?.track('Added to Cart', {
  ProductName: product.name,
  Price: product.price,
});
```

This check needs the default reload on revocation. With reloadOnConsentRevoked: false, check the categories before each call.

### Register it with your existing setup

#### Next.js

Add scripts to c15t.config.ts, next to next.config.ts. Keep the rest of your config, such as mode, in the same call. App Router, Pages Router and static export read the same file.

```
import { defineConsentConfig } from 'c15t/next';
import { scripts } from './src/consent-scripts';

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

#### TanStack Start

Pass scripts to the ConsentRoot in your root route. Import helpers in the route module, not in a server function: script configurations carry callbacks.

```
import { scripts } from '../consent-scripts';

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

#### React

Add scripts to the options of your ConsentProvider, next to mode. Keep the banner, dialog and preferences link inside the provider.

```
import { scripts } from './consent-scripts';

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

#### Nuxt

Register scripts under the c15t key in app.config.ts. Keep them out of nuxt.config.ts, which reaches the browser as JSON and drops the helper callbacks.

```
import { scripts } from '../src/consent-scripts';

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

#### Vue

Pass scripts to the c15tVue plugin call you have now. The plugin starts one script loader when the app mounts.

```
import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
import { scripts } from './consent-scripts';

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

#### Astro

Add scripts to the client entrypoint, which the integration finds on its own. Keep helpers out of astro.config.mjs: its options are serialized into the page.

```
import type { C15tClientOptionsExtension } from 'c15t/astro';
import { scripts } from './consent-scripts';

export default { scripts } satisfies C15tClientOptionsExtension;
```

#### Svelte

Pass scripts as a prop to the ConsentProvider you have now. The provider owns the loader and disposes it on unmount.

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

<ConsentProvider mode={manifest()} {scripts}>
  <!-- Your app and consent UI -->
</ConsentProvider>
```

#### SvelteKit

Add scripts to the ConsentRoot in the root layout. Import helpers in the layout component, not in +layout.server.ts: a server load cannot send functions to the browser.

```
<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()}
</ConsentRoot>
```

#### HTML: Paste the vendor’s snippet behind consent

The helpers need a bundler. On a page with the c15t script tag, paste the vendor’s own snippet instead and keep it inert until visitors allow its category. Helper options, such as loadMode, do not apply.

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

#### JavaScript

Pass scripts to init() from @c15t/browser, next to your mode. With createConsentRuntime, pass scripts to it instead.

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

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

Remove the Klaviyo snippet first. On Shopify, BigCommerce, WooCommerce and other platforms where Klaviyo adds Klaviyo.js for you, turn that off.

## Verify your setup

Filter DevTools Network by klaviyo. Then test the three states.

- **Before a choice**: Allow marketing only. Nothing loads from Klaviyo, and the Application tab shows no \_\_kla\_id cookie.

- **After you allow both**: Allow measurement too. klaviyo.js loads from static.klaviyo.com/onsite/js/ with your key, followed by its modules.

- **After you withdraw consent**: c15t removes the loader and reloads the page. The new page loads nothing from Klaviyo.

Cookie consent is not subscription consent. Your Klaviyo forms still need their own email and SMS consent, in the words that your channels need.

[Open the full Klaviyo guide](https://c15t.com/docs/integrations/klaviyo)

## Related guides

- [Marketo Munchkin](/integrations/marketo)

- [Oracle Eloqua](/integrations/oracle-eloqua)

- [Salesforce Account Engagement](/integrations/salesforce-pardot)