---
title: Script Loader
description: Gate third-party scripts behind consent — load Google Analytics, Meta Pixel, and other tracking scripts only when users grant permission.
---
The script loader manages third-party scripts based on consent state. Scripts are defined in the provider's `scripts` option and are automatically loaded when their required consent category is granted, and unloaded when consent is revoked.

c15t has a collection of premade scripts available in `@c15t/scripts`. Check the [integrations overview](/docs/integrations/overview) first before manually building a script.

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

> ℹ️ **Info:**
> We recommend using the pre-built integrations when possible.
>
> ℹ️ **Info:**
> If you need a vendor we do not ship yet, see the custom integration guide. It covers both one-off Script objects and reusable manifest-backed integrations.
>
> ℹ️ **Info:**
> For app-specific scripts, use a plain Script object. For reusable integrations, prefer a manifest-backed helper so startup phases, consent signaling, and future server-side loading support stay structured.

## Basic Usage

Pass an array of `Script` objects to the runtime options:

```ts
import { getOrCreateConsentRuntime } from 'c15t';
import { metaPixel } from '@c15t/scripts/meta-pixel';

const { consentStore } = getOrCreateConsentRuntime({
  mode: 'hosted',
  backendURL: 'https://your-instance.c15t.dev',
  scripts: [
    metaPixel({ pixelId: '123456' }),
    {
      id: 'custom-analytics',
      src: 'https://cdn.example.com/analytics.js',
      category: 'measurement',
    },
  ],
});
```

## Choose the Right Approach

* Use a plain `Script` for one-off app code.
* Use a manifest-backed helper in `@c15t/scripts` for reusable integrations, contributions, or anything that needs structured startup behavior.

If you are building something reusable, start with the [custom integration guide](/docs/integrations/building-integrations) before using raw callbacks.

## Reusable Integrations

For app-specific use, raw `Script` objects are usually enough.

For reusable integrations, c15t uses a manifest-backed model in `@c15t/scripts`. That keeps startup phases, consent signaling, and vendor-specific boot logic structured instead of hidden inside large callback bodies.

If you are building an integration for multiple apps or contributing upstream, use the [custom integration guide](/docs/integrations/building-integrations).

## Script Types

### Standard Scripts

Load an external JavaScript file via a `<script>` tag. Use `src` to specify the URL.

### Inline Scripts

Execute inline JavaScript code. Use `textContent` instead of `src`:

```tsx
{
  id: 'gtag-config',
  textContent: `
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXX');
  `,
  category: 'measurement',
}
```

### Callback-Only Scripts

Don't inject any `<script>` tag - just execute callbacks based on consent changes. Useful for controlling libraries that are already loaded:

```tsx
{
  id: 'posthog-consent',
  callbackOnly: true,
  category: 'measurement',
  onLoad: ({ hasConsent }) => {
    if (hasConsent) {
      posthog.opt_in_capturing();
    }
  },
  onConsentChange: ({ hasConsent }) => {
    if (hasConsent) {
      posthog.opt_in_capturing();
    } else {
      posthog.opt_out_capturing();
    }
  },
}
```

## Consent Conditions

The `category` field accepts a `HasCondition` - either a simple string or a logical expression:

```tsx
// Simple: requires measurement consent
{ category: 'measurement' }

// AND: requires both measurement and marketing
{ category: { and: ['measurement', 'marketing'] } }

// OR: requires either measurement or marketing
{ category: { or: ['measurement', 'marketing'] } }
```

## Script Callbacks

Every script supports four lifecycle callbacks:

| Callback          | When                                          | Use Case                 |
| ----------------- | --------------------------------------------- | ------------------------ |
| `onBeforeLoad`    | Before the script tag is injected             | Set up global variables  |
| `onLoad`          | Script loaded successfully                    | Initialize the library   |
| `onError`         | Script failed to load                         | Log error, load fallback |
| `onConsentChange` | Consent state changed (script already loaded) | Toggle tracking on/off   |

```tsx
{
  id: 'analytics',
  src: 'https://analytics.example.com/v2.js',
  category: 'measurement',
  onBeforeLoad: ({ id }) => {
    console.log(`Loading script: ${id}`);
  },
  onLoad: ({ element }) => {
    window.analytics.init('my-key');
  },
  onError: ({ error }) => {
    console.error('Failed to load analytics:', error);
  },
  onConsentChange: ({ hasConsent, consents }) => {
    window.analytics.setConsent(hasConsent);
  },
}
```

## Advanced Options

### Always Load

Scripts that manage their own consent internally (like GTM in consent mode):

```tsx
{
  id: 'google-tag-manager',
  src: 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXX',
  category: 'measurement',
  alwaysLoad: true, // Loads regardless of consent state
}
```

### Persist After Revocation

Keep the script loaded even after consent is revoked (the page won't reload for this script):

```tsx
{
  id: 'error-tracking',
  src: 'https://errors.example.com/track.js',
  category: 'measurement',
  persistAfterConsentRevoked: true,
}
```

### Script Placement

Control where in the DOM the script is injected:

```tsx
{
  id: 'widget',
  src: 'https://widget.example.com/embed.js',
  category: 'experience',
  target: 'body', // 'head' (default) or 'body'
}
```

### Ad Blocker Evasion

Script element IDs are anonymized by default to avoid ad blocker pattern matching:

```tsx
{
  id: 'analytics',
  src: '...',
  category: 'measurement',
  anonymizeId: true, // default: true
}
```

## Dynamic Script Management

Add, remove, or check scripts at runtime via the store:

```ts
const state = consentStore.getState();

// Add scripts dynamically
state.setScripts([
  { id: 'dynamic', src: 'https://cdn.example.com/widget.js', category: 'measurement' },
]);

// Remove a script
state.removeScript('dynamic');

// Check if a script is loaded
const loaded = state.isScriptLoaded('custom-analytics');

// Get all loaded script IDs
const allLoaded = state.getLoadedScriptIds();
```

## API Reference

| Property                   | Type                                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Default |  Required  |
| :------------------------- | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :--------: |
| id                         | string                                            | Unique identifier for the script                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | -       | ✅ Required |
| src                        | string \| undefined                               | URL of the script to load                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | -       |  Optional  |
| textContent                | string \| undefined                               | Inline JavaScript code to execute                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | -       |  Optional  |
| category                   | HasCondition\<AllConsentNames>                    | Consent category or condition required to load this script                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | -       | ✅ Required |
| callbackOnly               | boolean \| undefined                              | Whether this is a callback-only script that doesn't need to load an external resource.&#xA;When true, no script tag will be added to the DOM, only callbacks will be executed.&#xA;&#xA;This is useful for:&#xA;- Managing consent for libraries already loaded on the page&#xA;- Enabling/disabling tracking features based on consent changes&#xA;- Running custom code when consent status changes without loading external scripts&#xA;&#xA;Example use cases:&#xA;- Enabling/disabling Posthog tracking&#xA;- Configuring Google Analytics consent mode&#xA;- Managing cookie consent for embedded content | false   |  Optional  |
| persistAfterConsentRevoked | boolean \| undefined                              | Whether the script should persist after consent is revoked.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | false   |  Optional  |
| alwaysLoad                 | boolean \| undefined                              | Whether the script should always load regardless of consent state.&#xA;&#xA;This is useful for scripts like Google Tag Manager or PostHog that manage&#xA;their own consent state internally. The script will load immediately and&#xA;never be unloaded based on consent changes.&#xA;&#xA;Note: When using this option, you are responsible for ensuring the script&#xA;itself respects user consent preferences through its own consent management.                                                                                                                                                          | false   |  Optional  |
| fetchPriority              | "high" \| "low" \| "auto" \| undefined            | Priority hint for browser resource loading                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | -       |  Optional  |
| attributes                 | Record\<string, string> \| undefined              | Additional attributes to add to the script element                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | -       |  Optional  |
| async                      | boolean \| undefined                              | Whether to use async loading                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | -       |  Optional  |
| defer                      | boolean \| undefined                              | Whether to defer script loading                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | -       |  Optional  |
| nonce                      | string \| undefined                               | Content Security Policy nonce                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | -       |  Optional  |
| anonymizeId                | boolean \| undefined                              | Whether to use an anonymized ID for the script element, this helps ensure the script is not blocked by ad blockers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | true    |  Optional  |
| target                     | "head" \| "body" \| undefined                     | Where to inject the script element in the DOM.&#xA;- \`'head'\`: Scripts are appended to \`\<head>\` (default)&#xA;- \`'body'\`: Scripts are appended to \`\<body>\`&#xA;&#xA;Use \`'body'\` for scripts that:&#xA;- Need to manipulate DOM elements that don't exist until body loads&#xA;- Should load after page content for performance reasons&#xA;- Are required by third-party services to be in the body&#xA;&#xA;Use \`'head'\` (default) for scripts that:&#xA;- Need to track early page events (analytics)&#xA;- Should be available before page render&#xA;- Most tracking/analytics scripts       | 'head'  |  Optional  |
| onBeforeLoad               | ((info: ScriptCallbackInfo) => void) \| undefined | Callback executed before the script is loaded                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | -       |  Optional  |
| onLoad                     | ((info: ScriptCallbackInfo) => void) \| undefined | Callback executed when the script loads successfully                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | -       |  Optional  |
| onError                    | ((info: ScriptCallbackInfo) => void) \| undefined | Callback executed if the script fails to load                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | -       |  Optional  |
| onConsentChange            | ((info: ScriptCallbackInfo) => void) \| undefined | Callback executed whenever the consent store is changed.&#xA;This callback only applies to scripts already loaded.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | -       |  Optional  |
| vendorId                   | string \| number \| undefined                     | IAB TCF vendor ID - links script to a registered vendor.&#xA;&#xA;When in IAB mode, the script will only load if this vendor has consent.&#xA;Takes precedence over \`category\` when in IAB mode.&#xA;Use custom vendor IDs (string or number) to gate non-IAB vendors too.                                                                                                                                                                                                                                                                                                                                    | -       |  Optional  |
| iabPurposes                | number\[] \| undefined                            | IAB TCF purpose IDs this script requires consent for.&#xA;&#xA;When in IAB mode and no vendorId is set, the script will only load&#xA;if ALL specified purposes have consent.                                                                                                                                                                                                                                                                                                                                                                                                                                   | -       |  Optional  |
| iabLegIntPurposes          | number\[] \| undefined                            | IAB TCF legitimate interest purpose IDs.&#xA;&#xA;These purposes can operate under legitimate interest instead of consent.&#xA;The script loads if all iabPurposes have consent OR all iabLegIntPurposes&#xA;have legitimate interest established.                                                                                                                                                                                                                                                                                                                                                              | -       |  Optional  |
| iabSpecialFeatures         | number\[] \| undefined                            | IAB TCF special feature IDs this script requires.&#xA;&#xA;Special features require explicit opt-in:&#xA;- 1: Use precise geolocation data&#xA;- 2: Actively scan device characteristics for identification                                                                                                                                                                                                                                                                                                                                                                                                     | -       |  Optional  |
