Kjeks
← All docs

Kjeks — core plugin

The consent engine for WordPress: categories, prior blocking, the banner, and the tracker inventory. Single-site or multisite.

Requires WordPress 6.8+PHP 8.3+Single-site & multisite Source on GitHub →

101 Getting started

Install & activate

Download the latest kjeks.zip, then in WordPress go to Plugins → Add New → Upload Plugin and upload it.

  • Single site: activate it from Plugins. Admin lives under Settings → Cookie Consent.
  • Multisite: the plugin declares Network: true — Network Activate it. Admin lives under Network Admin → Settings → Cookie Consent.
On multisite, activation seeds one network-wide settings record and new subsites inherit tracker definitions lazily — nothing non-essential is copied per site.

Kjeks then updates itself from GitHub releases via plugin-update-checker — new versions appear on your Plugins screen like any other update.

Show the banner & preferences

The consent banner renders automatically in the site footer. To let visitors reopen their choices or publish a cookie table, drop in a block or shortcode:

PurposeBlockShortcode
Reopen the consent banner kjeks/preferences [kjeks_preferences]
Cookie declaration table kjeks/cookie-declaration [kjeks_cookie_declaration]

Set your content

On the Cookie Consent admin page you set the banner heading and body, your privacy-policy URL, and an accent colour. These are stored once and resolved per site.

WordPress privacy page

Kjeks plugs into WordPress' built-in privacy tools:

  • Policy Guide: under Settings → Privacy → Policy Guide Kjeks adds suggested text describing the consent categories and how consent works, so you can paste it into your own privacy policy.
  • Live declaration on the privacy page (opt-in): enable Show the cookie declaration on the privacy policy page on the network Settings tab and Kjeks appends the live cookie declaration table to the page set as your WordPress privacy policy. It is skipped automatically when the kjeks/cookie-declaration block or [kjeks_cookie_declaration] shortcode is already on that page, so the table is never duplicated.
The Policy Guide text is static suggested copy (WordPress copies it once). For an always-current table on a public page, use the block, the shortcode, or the opt-in auto-append above.

The consent categories

SlugDefaultMeaning
necessaryAlways onEssential; cannot be switched off.
preferencesDeniedRemembers choices like language or layout.
analyticsDeniedMeasurement and statistics.
marketingDeniedAdvertising and cross-site tracking.

Everything except necessary is denied until the visitor opts in. You can add categories with the kjeks_categories filter (see 301), but every non-necessary category is always optional.

Banner not showing? Usually by design — Kjeks skips it when a choice is already stored (~6 months; the visitor sees the Cookie settings trigger instead), or when the browser sends Global Privacy Control (Brave sends GPC by default) and Kjeks honours it — auto-rejecting non-essential technologies (disable with the kjeks_honor_gpc filter). Browser features that hide cookie notices (Brave Shields, uBlock cookie-notice lists) can also remove it.

201 Consent & integrations

Register an integration

Kjeks fires the kjeks_register_integrations action on init (priority 20). Register your trackers inside it with kjeks_register_integration( $id, $args ) — Kjeks then holds those scripts back until the matching category is granted.

add_action( 'kjeks_register_integrations', function () {
    kjeks_register_integration( 'matomo', [
        'category'    => 'analytics',
        'label'       => 'Matomo',
        'handles'     => [ 'matomo-tracker' ],
        'src_scripts' => [
            'https://cdn.example.com/matomo.js',
            [
                'src'   => 'https://cdn.example.com/extra.js',
                'attrs' => [ 'data-site' => '7' ],
            ],
        ],
        'inline'      => [ 'console.log( "loaded after consent" );' ],
    ] );
} );

Integration arguments

KeyTypeDescription
categorystringOne of preferences, analytics, marketing. Unknown or necessary falls back to marketing.
labelstringHuman-readable name shown in the cookie declaration.
handlesstring[]Registered script handles to gate (blocks an already-enqueued script until consent).
src_scriptsarrayExternal scripts to inject after consent. A plain URL string, or { src, attrs } to add attributes.
inlinestring[]Inline JS snippets to run after consent.

Helper functions

Prefer a one-liner? These wrap the same machinery:

FunctionUse
kjeks_enqueue_script( $handle, $src, $category, $deps = [] )Enqueue a script that loads only after the category is granted.
kjeks_add_inline_script( $category, $code, $id = '' )Run inline JS gated by a category.
kjeks_embed( $src, $category, $args = [] )Return consent-gated embed markup (iframes, pixels).
kjeks_is_granted( $category )Boolean check for server-side branching.
kjeks_preferences_link( $label = '', $display = true )Output/return a link that reopens the banner.
// Enqueue a script that only loads after a category is granted.
kjeks_enqueue_script( 'my-analytics', 'https://cdn.example.com/a.js', 'analytics' );

// Add inline JS gated by a category.
kjeks_add_inline_script( 'marketing', 'window.startPixel();' );

// Render a consent-gated embed (e.g. a YouTube iframe).
echo kjeks_embed( 'https://www.youtube.com/embed/VIDEO_ID', 'marketing' );

// Branch your own PHP on a granted category.
if ( kjeks_is_granted( 'analytics' ) ) {
    // …
}

// Output a "Cookie preferences" link that reopens the banner.
kjeks_preferences_link( 'Cookie preferences' );

Consent on the front end

The visitor's choice is stored in a cookie and localStorage under the key kjeks_consent, with a six-month lifetime. When consent changes, Kjeks dispatches events you (and add-ons) can listen for:

window.addEventListener( 'kjeks:granted', () => {
    // Fired when the visitor grants (or updates) consent.
} );

window.addEventListener( 'kjeks:withdrawn', () => {
    // Fired when the visitor withdraws consent.
} );
Multisite: trackers are defined network-wide and resolved per blog. A tracker with an empty sites list applies to the whole network; otherwise it applies only to the listed blog IDs.

301 Hooks, REST & CLI

Filters

FilterDefaultPurpose
kjeks_categories—Add or modify category definitions.
kjeks_privacy_urlPolicy URLOverride the resolved privacy-policy URL (receives blog ID).
kjeks_banner_contentStored contentOverride the resolved banner content (receives blog ID).
kjeks_honor_gpctrueWhether to honour the browser's Global Privacy Control signal.
kjeks_is_granted—Override the server-side category-grant check.
kjeks_server_side_gatingfalseEnable conditional server-side gating of output.
kjeks_embed_html—Modify consent-gated embed markup.

WP-CLI

wp kjeks scan-config

Generate a scanner config from the sites and paths WordPress knows about.

wp kjeks scan-config [--paths=<paths>] [--output=<file>] [--include=<ids>]

Outputs { sites: [ { url, blog_id, policy_version, paths } ] } — feed it straight to the scanner.

wp kjeks import

Import reviewed scanner observations back into the tracker inventory.

wp kjeks import <file> [--blog_id=<id>]

The file accepts either shape:

# A single site
{
  "blog_id": 1,
  "observations": [ /* … tracker observations … */ ]
}

# Or several sites at once
{
  "sites": [
    { "blog_id": 1, "observations": [ /* … */ ] },
    { "blog_id": 2, "observations": [ /* … */ ] }
  ]
}

wp kjeks scan-key

Manage the shared key the scanner uses to authenticate against scan-config and import. The key is sent in the X-Kjeks-Key header, so it works even behind proxies that strip Authorization (the usual cause of 401 rest_not_logged_in).

wp kjeks scan-key --generate   # rotate + print the key (store as KJEKS_SCAN_KEY)
wp kjeks scan-key              # print the current key
wp kjeks scan-key --clear      # disable key auth

REST API

All routes live under the kjeks/v1 namespace and require manage_network on multisite, or manage_options on a single site. The scan-config and import routes also accept the shared scanner key (X-Kjeks-Key header or scan_key query argument) in place of a capability.

Method & routeBody / query
GET /network-configReturns the current configuration.
POST /network-configreviews, remove, add, content, deleteOnUninstall, bannerDefaultVisible, privacyPageDeclaration.
GET /scan-configOptional include and paths (comma-separated).
POST /importRequired blog_id and observations.

Options & tracker schema

OptionScopeShape
kjeks_network_trackersNetworkTracker records keyed by tracker ID.
kjeks_network_contentNetwork{ heading, body, privacy_url, accent }
kjeks_network_settingsNetwork{ delete_on_uninstall, banner_default_visible, privacy_page_declaration }
kjeks_policy_versionPer siteInteger; bumping it re-prompts visitors.

Each tracker record carries:

id, name, category, reviewed, provider, purpose, party, storage_type, domain, path, retention, source, documentation_url, first_observed, last_observed, and sites.

Next: automate discovery with the scanner, speed up review with the AI Reviewer, or wire Google tags via Google Consent Mode.