npm package: @katla.app/sdk
Description: TypeScript SDK for building custom cookie consent flows with Katla.
Latest version: 0.4.3

# @katla.app/sdk

TypeScript SDK for building custom cookie consent flows with Katla.

Get started:
- Create your account and site at [katla.app](https://katla.app)
- Read full guides at [docs.katla.app](https://docs.katla.app)

## Install

```bash
npm install @katla.app/sdk
```

Requirements:
- Node.js 24+
- React 18+ or 19+ (only for React/Next integrations)

## Basic usage

```ts
import { createKatlaClient } from '@katla.app/sdk';

const client = createKatlaClient({
  siteId: 'your-site-id',
  // baseUrl: 'https://dist.katla.app', // optional override
});

const cookies = await client.getCookies();
const manifest = await client.getManifest();

const unsubscribe = client.onConsentChange((consent) => {
  console.log('Updated consent:', consent);
});

await client.injectGuard();

unsubscribe();
```

## React usage

```tsx
import {
  KatlaProvider,
  useKatlaCookies,
} from '@katla.app/sdk/react';

function CookieList() {
  const { cookies, loading, error } = useKatlaCookies();

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Failed: {error.message}</p>;
  if (!cookies) return null;

  return <pre>{JSON.stringify(cookies.cookies, null, 2)}</pre>;
}

export function App() {
  return (
    <KatlaProvider siteId="your-site-id">
      <CookieList />
    </KatlaProvider>
  );
}
```

`KatlaProvider` includes `KatlaGuard` and `ConsentBridge` automatically. Individual components are still exported for advanced use cases where you need full control.

## Consent UI components

The SDK ships ready-to-use `<CookieBanner>` and `<CookieCatalog>` components plus a `useConsentManager` hook. All components are **unstyled** — they render semantic HTML with stable `katla-*` class names so you can apply your own CSS.

### Quick start

Drop in a banner and catalog with zero configuration:

```tsx
import {
  KatlaProvider,
  CookieBanner,
  CookieCatalog,
  useKatlaCookies,
} from '@katla.app/sdk/react';

function CookiePage() {
  const { cookies, loading, error } = useKatlaCookies();

  return (
    <>
      <CookieCatalog data={cookies} loading={loading} error={error} />
      <CookieBanner />
    </>
  );
}

export function App() {
  return (
    <KatlaProvider siteId="your-site-id">
      <CookiePage />
    </KatlaProvider>
  );
}
```

### CookieBanner

Renders a consent banner with accept/reject/customize controls.

```tsx
<CookieBanner
  title="We use cookies"
  description="Choose which categories you'd like to allow."
  labels={{
    acceptAll: 'Accept all',
    rejectAll: 'Reject non-essential',
    saveSelection: 'Save selection',
    functionalBadge: 'Functional always on',
    privacyTrigger: 'Privacy preferences',
  }}
  className="my-banner"
/>
```

Props:

| Prop | Type | Default |
|------|------|---------|
| `title` | `string` | `"Cookie preferences"` |
| `description` | `ReactNode` | Brief consent copy |
| `labels` | `object` | See above defaults |
| `className` | `string` | — |
| `children` | `(state: ConsentManagerState) => ReactNode` | — |

When `children` is provided as a render prop, the default UI is replaced entirely and you receive the full `ConsentManagerState`:

```tsx
<CookieBanner>
  {({ acceptAll, rejectAll, open, setOpen, selected, toggleCategory, saveSelection }) => (
    <div className="my-custom-banner">
      <button onClick={acceptAll}>OK</button>
      <button onClick={rejectAll}>No thanks</button>
      <button onClick={() => setOpen(!open)}>Customize</button>
      {/* ... */}
    </div>
  )}
</CookieBanner>
```

CSS class names (for styling the default UI):

- `katla-banner` — root `<aside>`
- `katla-banner-copy` — title + description wrapper
- `katla-banner-controls` — button area in preferences panel
- `katla-toggle-grid` — category toggle container
- `katla-toggle-card` — individual category toggle
- `katla-pill` — "always on" badge
- `katla-button-row` — row of action buttons
- `katla-button-accept`, `katla-button-reject`, `katla-button-save`, `katla-button-customize` — individual buttons
- `katla-privacy-trigger` — re-open button shown after consent

### CookieCatalog

Displays cookies grouped by category.

```tsx
import { useKatlaCookies, CookieCatalog } from '@katla.app/sdk/react';

function CookieList() {
  const { cookies, loading, error } = useKatlaCookies();
  return <CookieCatalog data={cookies} loading={loading} error={error} />;
}
```

Props:

| Prop | Type | Default |
|------|------|---------|
| `data` | `CookieData \| null` | — |
| `loading` | `boolean` | `false` |
| `error` | `Error \| string \| null` | — |
| `maxPerCategory` | `number` | `5` |
| `className` | `string` | — |
| `emptyMessage` | `string` | `"No cookies found."` |
| `renderCategory` | `(category, cookies) => ReactNode` | — |
| `renderCookie` | `(cookie, category) => ReactNode` | — |

Customize per-cookie rendering while keeping the grid layout:

```tsx
<CookieCatalog
  data={cookies}
  loading={loading}
  error={error}
  maxPerCategory={10}
  renderCookie={(cookie) => (
    <div>
      <code>{cookie.name}</code> — {cookie.domain}
    </div>
  )}
/>
```

CSS class names:

- `katla-catalog-grid` — root grid container
- `katla-catalog-card` — category card
- `katla-catalog-card-header` — card header with category name + count
- `katla-empty-card` — shown when no cookies exist

### useConsentManager

Headless hook for building fully custom consent UIs. Both `CookieBanner` and `CookieCatalog` are built on top of this.

```tsx
import { useConsentManager } from '@katla.app/sdk/react';

function MyConsentUI() {
  const {
    ready,            // true once window.KatlaConsent is available
    hasDecision,      // true after user has accepted/rejected
    open,             // preferences panel visibility
    setOpen,
    selected,         // currently toggled-on categories
    availableCategories, // categories with cookies (excludes functional/unknown)
    toggleCategory,
    acceptAll,        // calls KatlaConsent.acceptAll() + closes panel
    rejectAll,        // calls KatlaConsent.rejectAll() + closes panel
    saveSelection,    // calls KatlaConsent.acceptCategories() + closes panel
    cookies,          // { data, loading, error }
  } = useConsentManager();

  if (!ready) return null;

  return (
    <div>
      {!hasDecision && (
        <div>
          <p>We use cookies.</p>
          <button onClick={acceptAll}>Accept all</button>
          <button onClick={rejectAll}>Reject all</button>
        </div>
      )}

      {open && (
        <div>
          {availableCategories.map((cat) => (
            <label key={cat}>
              <input
                type="checkbox"
                checked={selected.includes(cat)}
                onChange={() => toggleCategory(cat)}
              />
              {cat}
            </label>
          ))}
          <button onClick={saveSelection}>Save</button>
        </div>
      )}
    </div>
  );
}
```

The `ManageableCategory` type (`'personalization' | 'analytics' | 'marketing' | 'security'`) is also exported for type-safe category handling.

## Next.js usage

Client provider:

```tsx
import { KatlaNextProvider } from '@katla.app/sdk/next';

export default function Providers({ children }: { children: React.ReactNode }) {
  return <KatlaNextProvider siteId="your-site-id">{children}</KatlaNextProvider>;
}
```

Server-side cached fetch:

```tsx
import { getCachedCookies } from '@katla.app/sdk/next/server';

export default async function Page() {
  const data = await getCachedCookies({ siteId: 'your-site-id' });
  return <pre>{JSON.stringify(data.cookies, null, 2)}</pre>;
}
```

All consent UI components (`CookieBanner`, `CookieCatalog`, `useConsentManager`) are available from `@katla.app/sdk/next` and work identically to the React versions:

```tsx
"use client";

import {
  CookieBanner,
  CookieCatalog,
  useKatlaCookies,
} from '@katla.app/sdk/next';

export default function ConsentPage() {
  const { cookies, loading, error } = useKatlaCookies();

  return (
    <>
      <h1>Cookie information</h1>
      <CookieCatalog data={cookies} loading={loading} error={error} />
      <CookieBanner />
    </>
  );
}
```

## Configuration file

Create a `katla.config.mjs` (ESM) or `katla.config.js` (CJS) in your project root to set defaults for `katla pull`:

```js
// katla.config.mjs
export default {
  siteId: 'your-site-id',
  dir: '.katla',
  // locale: 'en-GB',
  // format: 'full',
};
```

```js
// katla.config.js (CommonJS)
module.exports = {
  siteId: 'your-site-id',
};
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `siteId` | `string` | — | Your site's UUID |
| `dir` | `string` | `'.katla'` | Output directory for pulled files |
| `baseUrl` | `string` | `'https://dist.katla.app'` | Override the CDN base URL |
| `locale` | `string` | — | Pull a single locale only (e.g., `'en-GB'`). Omit to pull all. |
| `format` | `string` | `'full'` | Policy format: `'full'`, `'cookie'`, or `'table'` |

CLI flags always override config file values. With a config file, `katla pull` works without arguments.

## Static cookie data (build-time)

CLI:

```bash
katla pull [site-id]
# Outputs: .katla/cookies.json, .katla/cookies.*.json, .katla/policy.*.json, .katla/guard.js, .katla/manifest.json
```

The `site-id` argument is optional when a config file is present.

Programmatic:

```ts
import { fetchStaticAll } from '@katla.app/sdk/static';

const { cookies, policies, guard, manifest } = await fetchStaticAll({
  siteId: 'your-site-id',
});
```

Use the generated `cookies.json` as `initialCookies` and `guard.js` as `guardScript` in `KatlaProvider`/`KatlaNextProvider`:

```tsx
// Vite (raw import)
import cookies from './.katla/cookies.json';
import guardScript from './.katla/guard.js?raw';

<KatlaProvider siteId="your-site-id" initialCookies={cookies} guardScript={guardScript}>
```

```tsx
// Next.js (server helper)
import { getStaticGuardScript } from '@katla.app/sdk/next/server';

const guardScript = await getStaticGuardScript();

<KatlaNextProvider siteId="your-site-id" initialCookies={cookies} guardScript={guardScript}>
```

## Policies

Fetch auto-generated policy documents in any supported locale:

```ts
const client = createKatlaClient({ siteId: 'your-site-id' });

// Fetch combined cookie + privacy policy
const policy = await client.getPolicy({ locale: 'de-DE', format: 'full' });
console.log(policy.policy.markdown);

// Fetch cookie policy only
const cookiePolicy = await client.getPolicy({ format: 'cookie' });

// Fetch cookie tables only
const tables = await client.getPolicy({ format: 'table' });
```

Formats: `full` (combined cookie + privacy policy), `cookie` (cookie policy only), `table` (cookie tables only).

Supported locales: `en-GB`, `en-US`, `de-DE`, `fr-FR`, `es-ES`, `it-IT`, `nl-NL`, `pt-PT`, `sv-SE`, `nb-NO`, `da-DK`, `pl-PL`, `fi-FI`.

Build-time fetch (all locales):

```ts
import { fetchStaticAll } from '@katla.app/sdk/static';

const { cookies, policies, manifest } = await fetchStaticAll({
  siteId: 'your-site-id',
  // locale: 'de-DE',  // optional, omit to pull all locales
  // format: 'full',   // optional, default
});
```

CLI (`katla pull` fetches cookies + policies for all locales by default):

```bash
katla pull [site-id]
# Outputs: .katla/cookies.json, .katla/cookies.*.json, .katla/policy.*.json, .katla/guard.js, .katla/manifest.json

katla pull [site-id] --locale de-DE --format full
# Outputs: .katla/cookies.json, .katla/cookies.de-de.json, .katla/policy.de-de.json, .katla/guard.js, .katla/manifest.json
```

## CCPA / GPC support

Check if the visitor's browser has Global Privacy Control enabled and handle CCPA opt-outs:

```ts
import { isGPCEnabled } from '@katla.app/sdk';

if (isGPCEnabled()) {
  // Browser has GPC enabled — honor opt-out
  window.KatlaConsent?.optOutOfSale();
}

// Check active regulation
window.KatlaConsent?.getRegulation(); // 'gdpr' or 'ccpa'
```

## Documentation

- Product onboarding: [katla.app](https://katla.app)
- Developer docs: [docs.katla.app](https://docs.katla.app)