Configuration
Configuration Methods
Zest can be configured in three ways (in order of priority):
- Data attributes on the script tag - Highest priority
window.ZestConfigobject- Default values - Lowest priority
When both sources set categories, Zest merges them per category, so data-hide-categories doesn’t discard labels defined in window.ZestConfig.
Using ZestConfig Object
<script>
window.ZestConfig = {
position: 'bottom-right',
theme: 'auto',
accentColor: '#0071e3',
policyUrl: '/privacy-policy',
mode: 'safe',
expiration: 365
};
</script>
<script src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"></script>
Using Data Attributes
<script
src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"
data-position="bottom-right"
data-theme="auto"
data-accent-color="#0071e3"
data-policy-url="/privacy-policy"
data-mode="safe"
data-expiration="365"
data-auto-init="true"
data-show-widget="true"
data-geo="on"
data-geo-timeout="1500"
data-geo-fallback="consent"
data-branding="false"
data-button-style="outline"
data-button-layout="split"
data-backdrop-blur="8"
data-hard-wall="on"
data-intercept-embeds="false"
data-privacy-rewrite="false"
data-hide-categories="analytics,marketing"
data-privacy-url="/privacy"
data-imprint-url="/imprint"
data-consent-mode-google
data-consent-mode-microsoft
></script>
data-modeworks as of 2.8.0 — before that it was silently ignored.
Complete Options Reference
Every option, type, allowed value, and default is also published as a JSON Schema: zest.config.schema.json. Point your editor at it for validation and autocompletion of window.ZestConfig, or feed it to any tool that understands JSON Schema.
UI Options
| Option | Type | Default | Description |
|---|---|---|---|
position |
string | 'bottom' |
Banner position: 'bottom', 'bottom-left', 'bottom-right', 'top', 'top-left', 'top-right', 'center' (v2.6.0+) |
theme |
string | 'auto' |
Color theme: 'light', 'dark', 'auto' (follows system preference) |
accentColor |
string | '#0071e3' |
Primary button color. Validated — hex, named, rgb(), rgba(), hsl(), hsla() |
showWidget |
boolean | true |
Show floating widget after consent is given |
branding |
boolean | true |
Show a small “Powered by Zest” attribution link on the banner and settings modal. Set false (or data-branding="false") to remove it. Full build only (v2.4.0+) |
buttonStyle |
string | 'fill' |
Button style: 'fill' (solid) or 'outline' (accent-bordered transparent). Also accepted via data-button-style (v2.5.0+) |
buttonLayout |
string | 'row' |
Button arrangement: 'row' (all in one line), 'split' (settings on left, accept+reject on right), 'split-modern' (settings as primary, accept+reject as secondary). Also accepted via data-button-layout (v2.6.0+) |
backdropBlur |
number | 0 |
Blur radius in pixels for the page content behind the modal and hard wall overlay. 0 disables. Also accepted via data-backdrop-blur (v2.6.0+) |
hardWall |
boolean | false |
Full-viewport overlay behind the banner that blocks all page interaction until the visitor accepts or rejects. The banner becomes aria-modal when active. Also accepted via data-hard-wall (v2.6.0+) |
Behavior Options
| Option | Type | Default | Description |
|---|---|---|---|
autoInit |
boolean | true |
Automatically initialize on page load |
expiration |
number | 365 |
Consent cookie expiration in days (1-365). See How long consent lasts for the Safari cap |
mode |
string | 'safe' |
Script blocking mode (see below) |
privacyRewrite |
boolean | true |
Rewrite gated YouTube embeds to youtube-nocookie.com and Vimeo players with dnt=1 when activated. Use false or data-privacy-rewrite="false" to keep original URLs (v3.0.0+) |
blockedDomains |
array | [] |
Additional domains to block |
allowedDomains |
string[] | [] |
Domains that bypass all blocking (incl. subdomains). cookiezest.com always allowed |
Consent Mode Integrations
| Option | Type | Default | Description |
|---|---|---|---|
consentModeGoogle |
boolean | false |
Enable Google Consent Mode v2 integration |
consentModeMicrosoft |
boolean | false |
Enable Microsoft UET Consent Mode integration |
When enabled, Zest automatically pushes a 'default' denied state on initialization and sends 'update' signals on every consent change. See Consent Mode examples for usage.
Privacy Options
| Option | Type | Default | Description |
|---|---|---|---|
respectDNT |
boolean | true |
Respect Do Not Track and Global Privacy Control signals |
dntBehavior |
string | 'reject' |
DNT behavior: 'reject', 'preselect', 'ignore' |
geo |
boolean | object | null |
Opt-in geo / jurisdiction gating. true uses the hosted gateway; an object accepts provider, endpoint, resolver, decide, timeout, fallback. data-geo="on" is the attribute equivalent (v2.4.0+) |
Links
| Option | Type | Default | Description |
|---|---|---|---|
policyUrl |
string | null |
URL to privacy policy page. Validated — allowlist: http:, https:, mailto:, tel:, or relative |
imprintUrl |
string | null |
URL to imprint/legal page. Same validation as policyUrl |
Language Options
| Option | Type | Default | Description |
|---|---|---|---|
lang |
string | 'auto' |
Language code or 'auto' for detection |
Available languages: en, de, es, fr, it, pt, nl, pl, uk, ru, ja, zh
Advanced Options
| Option | Type | Default | Description |
|---|---|---|---|
customStyles |
string | '' |
Custom CSS to inject into Shadow DOM. Sanitized (20 KB cap; strips @import, @charset, expression(), -moz-binding, external url(), accept/reject-button selectors) |
labels |
object | {} |
Custom UI text labels, including labels.embed.title, .text (%category% inserts the localized category label), and .load for the embed overlay (v3.0.0+) |
categories |
object | {} |
Per-category overrides. Set hidden: true to remove a category from the settings modal — hidden categories are forced to false (rejected). Essential cannot be hidden. Also accepted via data-hide-categories="analytics,marketing" (v2.6.0+) |
callbacks |
object | {} |
Lifecycle callbacks. Exceptions are logged and swallowed |
intercept |
object | { cookies: true, storage: true, scripts: true, network: true, embeds: true } |
Disable interceptor channels individually. scripts controls script, link, image, and iframe element interception; network controls fetch, XHR, and beacon interception; embeds gates third-party iframes (set data-intercept-embeds="false" to disable). Channels since v2.2.0; network v2.3.0; embeds v3.0.0. See script blocking. |
essentialKeys |
array | [] |
Exact storage / cookie names to treat as strictly-necessary. Each is anchored as ^name$ and appended to the essential category — built-in essential patterns stay intact (v2.2.0+) |
essentialPatterns |
array | [] |
Regex source strings appended to the essential category. Validated via safeRegExp; 500-char cap (v2.2.0+) |
patterns |
object | {} |
Custom regex patterns for cookie categorization. Replaces the patterns for any category passed in. Prefer essentialKeys / essentialPatterns when you only want to add to the essential category. 500-char cap; catastrophic-backtracking patterns rejected |
Headless integration example
When you bring your own UI and want Zest only for the consent state machine:
import Zest from '@freshjuice/zest/headless';
Zest.init({
// Disable script blocking — you handle gating yourself
intercept: { scripts: false },
// Tell Zest your app's storage keys are strictly-necessary,
// so the storage interceptor doesn't classify them as marketing
// and block the user's explicit setItem() calls.
essentialKeys: ['my_app_settings', 'theme_pref'],
respectDNT: true,
expiration: 365
});
How Long Consent Lasts
Zest stores the visitor’s choice in a single first-party cookie so it survives page reloads and works with any backend. Know its shape, you will need it for your privacy policy, your CDN rules, and any server-side compliance logging.
| Name | zest_consent |
| Value | URL-encoded JSON: { "version": "1.0", "timestamp": 1730000000000, "categories": { "essential": true, "functional": false, "analytics": false, "marketing": false } } |
| Attributes | path=/, SameSite=Lax, Secure on HTTPS sites, expires per expiration |
| Deleted | Zest.reset() expires it immediately |
Your backend can read it server-side on every request, which is the reliable way to build a consent audit log; Zest.getConsentProof() returns the same payload from JS.
The Safari caveat: the expiration setting is the intended lifetime, not a guarantee: Zest sets the cookie from JavaScript, and Safari’s Intelligent Tracking Prevention caps all script-set cookies at 7 days. So every Safari visitor gets re-prompted at most a week after their last visit, no matter what you set. Chrome, Firefox, and Edge honor the full expiration. If week-long consent on Safari is a problem for your compliance model, mirror the cookie server-side (or use a Set-Cookie header from your backend) and re-issue it with a longer lifetime: a cookie set by the server is not capped.
Blocking Modes
Zest supports four script blocking modes:
| Mode | Description |
|---|---|
manual |
Only blocks scripts with explicit data-consent-category attribute |
safe |
Manual + known major trackers (Google Analytics, Facebook, GTM) |
strict |
Safe + extended trackers (Hotjar, Mixpanel, Segment, etc.) |
doomsday |
Block ALL third-party scripts |
window.ZestConfig = {
mode: 'safe' // Recommended for most sites
};
DNT Behavior Options
When respectDNT is true, the dntBehavior option controls what happens:
| Behavior | Description |
|---|---|
'reject' |
Auto-reject all non-essential cookies, don’t show banner |
'preselect' |
Show banner with non-essential categories unchecked |
'ignore' |
Ignore DNT/GPC signals completely |
If you use geo gating, the two features interact: see DNT, GPC and Geo for which one wins and when.
Custom Blocked Domains
Add additional domains to block:
window.ZestConfig = {
mode: 'safe',
blockedDomains: [
'custom-tracker.com',
{ domain: 'analytics-service.com', category: 'analytics' }
]
};
Custom Allowed Domains (v2.7.0+)
Domains in allowedDomains bypass all blocking — mode, blockedDomains, and doomsday third-party blocking. Subdomains included ('example.com' matches 'api.example.com'). cookiezest.com is always allowed and doesn’t need to be listed.
window.ZestConfig = {
mode: 'doomsday',
allowedDomains: ['my-cdn.com', 'trusted-analytics.com']
};
Custom Labels
Override any UI text:
window.ZestConfig = {
labels: {
banner: {
title: 'Cookie Settings',
description: 'We use cookies to improve your experience.',
acceptAll: 'Accept All',
rejectAll: 'Reject All',
settings: 'Customize'
},
modal: {
title: 'Privacy Preferences',
save: 'Save Preferences',
acceptAll: 'Accept All',
rejectAll: 'Reject All'
},
widget: {
label: 'Cookie Settings'
},
notice: {
title: 'Your Privacy Choices',
optOut: 'Do Not Sell or Share My Personal Information',
dismiss: 'Dismiss'
},
embed: {
title: 'External content is blocked',
text: 'Content in the %category% category does not load until you consent.',
load: 'Load content'
}
}
};
The notice labels apply to the “Do Not Sell” notice shown for the geo 'notice' action (v2.4.0+).
Callbacks
React to consent lifecycle events:
window.ZestConfig = {
callbacks: {
onReady: (consent) => {
console.log('Zest initialized:', consent);
},
onAccept: (consent) => {
console.log('User accepted:', consent);
},
onReject: () => {
console.log('User rejected all');
},
onChange: (consent) => {
console.log('Consent changed:', consent);
},
onGeo: (action, verdict) => {
// v2.4.0+ — fires once geo resolution completes (when geo is enabled)
console.log('Geo action:', action, verdict);
}
}
};
Full Example
window.ZestConfig = {
// UI
position: 'bottom-right',
theme: 'auto',
accentColor: '#0071e3',
showWidget: true,
buttonStyle: 'fill', // 'fill' or 'outline' (v2.5.0+)
buttonLayout: 'row', // 'row', 'split', 'split-modern' (v2.6.0+)
backdropBlur: 0, // blur radius in px, 0 disables (v2.6.0+)
hardWall: false, // block page until decided (v2.6.0+)
// Behavior
autoInit: true,
expiration: 365,
mode: 'safe',
privacyRewrite: true, // privacy-enhanced video URLs (v3.0.0+)
intercept: { embeds: true }, // gate third-party iframes (v3.0.0+)
// Consent Mode
consentModeGoogle: true,
consentModeMicrosoft: false,
// Privacy
respectDNT: true,
dntBehavior: 'reject',
geo: true, // opt-in jurisdiction gating (v2.4.0+)
// Links
policyUrl: '/privacy-policy',
imprintUrl: '/legal',
// Language
lang: 'auto',
// Categories — hide unused categories from the modal (v2.6.0+)
categories: {
marketing: { hidden: true }
},
// Callbacks
callbacks: {
onAccept: (consent) => {
if (consent.analytics) {
// Initialize analytics
}
}
}
};