---
title: "Headless"
url: "https://cookiezest.com/docs/headless"
description: "Run the Zest consent engine with your own UI"
---

_Since v2.0.0. Headless API surface complete since v2.8.0._

## Overview

The headless entry runs the full consent engine with zero UI. No banner, no settings modal, no widget, no translations, no injected stylesheet, no Shadow DOM. You render the consent surface yourself in whatever framework you already use, and Zest handles the parts that are hard to get right: cookie and storage interception, script blocking, embed gating, DNT/GPC signals, geo resolution, and the consent record itself.

```
import Zest from '@freshjuice/zest/headless';

const snapshot = Zest.init({ mode: 'safe', policyUrl: '/privacy-policy' });

if (!snapshot.hasDecision && !snapshot.dntApplied) {
  myBanner.show();
}
```

Key differences from the full build:

|  | Full build | Headless |
| --- | --- | --- |
| Consent engine, interceptors, script blocking | Yes | Yes |
| Banner, modal, widget, “Do Not Sell” notice | Yes | No |
| Embed consent overlay | Yes | No |
| Zest.activateEmbed() | Yes | Yes (you render your own load control) |
| Translations (12 languages) | Yes | No |
| Auto-init on load | Yes | No, you call init() explicitly |
| window.Zest global | Yes | No |
| Bundle format | IIFE script tag + ESM | ESM only, ~16 KB gzipped |

## What’s Included

Everything except the UI methods (`show`, `hide`, `showSettings`, `hideSettings`):

```
Zest.init(config);          // initialize, returns a snapshot (see below)
Zest.getConsent();          // current categories
Zest.hasConsent('analytics');
Zest.hasConsentDecision();  // any decision recorded?
Zest.getConsentProof();     // signed proof payload for compliance logs
Zest.acceptAll();
Zest.rejectAll();
Zest.updateConsent({ analytics: true, marketing: false });
Zest.reset();               // clears the consent cookie (your UI decides what to do next)
Zest.resolveGeo();          // Promise of { action, verdict } when geo is configured
Zest.isDoNotTrackEnabled();
Zest.getDNTDetails();
Zest.activateEmbed(elementOrIndex);
Zest.on(event, fn);         // returns unsubscribe
Zest.once(event, fn);
Zest.getConfig();
Zest.version;
```

Named exports are also available if you tree-shake:

```
import { init, hasConsent, on } from '@freshjuice/zest/headless';
```

## The init() Snapshot

`Zest.init()` returns a snapshot you branch on. This is the whole wiring pattern for a custom UI:

```
const s = Zest.init({ geo: true });

if (s.hasDecision) {
  // Returning visitor with a recorded choice. Render nothing.
} else if (s.dntApplied) {
  // Visitor was auto-rejected by a DNT/GPC signal. Render nothing.
} else if (s.geoPending) {
  // Geo resolution in flight. Hold your UI until zest:geo fires,
  // then branch on the action (see the geo docs for headless details).
} else {
  showMyBanner();
}
```

| Field | Meaning |
| --- | --- |
| hasDecision | A consent choice exists in the zest_consent cookie |
| dntApplied | The visitor’s DNT/GPC signal auto-rejected all non-essential categories |
| geoPending | Geo is configured and the jurisdiction verdict is still in flight |
| consent | Current category state |
| alreadyInitialized | init() was called before; this call made no changes |

`init()` is idempotent: interceptors install exactly once, and a second call (a React StrictMode double-mount, for example) just returns the current state.

## Wiring Your Own UI

Subscribe to changes, drive your form from the state:

```
Zest.init({ mode: 'safe', essentialKeys: ['my_app_settings'] });

Zest.on('change', (consent) => {
  syncMyCheckboxes(consent);
});

mySaveButton.addEventListener('click', () => {
  Zest.updateConsent(readMyCheckboxes());
});
```

Blocked embeds surface through the `zest:embed` event instead of the built-in overlay, so render your own “load content” control:

```
Zest.on('embed', (e) => {
  if (e.detail.phase === 'blocked') {
    addMyLoadButton(e.detail.element, () => Zest.activateEmbed(e.detail.element));
  }
});
```

## Next Steps

-   [Getting Started: npm install](/docs/getting-started/#npm-headless-bring-your-own-ui) - Package installation
-   [Examples: headless walkthrough](/docs/examples/#headless-bring-your-own-ui) - A full React banner component
-   [Geo: headless](/docs/geo/#headless) - Jurisdiction gating without UI
-   [Events & Callbacks](/docs/events/) - Everything your UI can listen to