Kjeks
← All docs

Build an add-on

Wire any third-party technology into the Kjeks consent layer, using the Google Consent Mode add-on (kjeks-google) as the worked example.

Requires the Kjeks corePHP 8.3+ Source on GitHub →

A Kjeks add-on is just a normal WordPress plugin that declares its technologies to the core plugin. Kjeks then holds those scripts back until the visitor grants the matching consent category. You never write the blocking yourself — you describe what runs and which category it belongs to. Everything below maps to real code in kjeks-google.

1 · Anatomy of an add-on

Plugin header

Declare a dependency on the core plugin so WordPress itself warns when it's missing, and mark it network-capable:

<?php
/**
 * Plugin Name:       Kjeks Google
 * Description:       Google Tag Manager / GA4 for Kjeks via Consent Mode v2.
 * Requires at least: 6.8
 * Requires PHP:      8.3
 * Requires Plugins:  kjeks          // WordPress warns if Kjeks is missing
 * Network:           true           // multisite-friendly, like the core plugin
 */

Boot only when Kjeks is present

The Requires Plugins: kjeks header already stops WordPress from activating your add-on without Kjeks (and from deactivating Kjeks while yours is active). So you don't need a dependency class — a one-line function_exists() guard on the core plugin's public API is enough insurance for the rare “active but not loaded” edge:

add_action(
    'plugins_loaded',
    static function (): void {
        // The Requires Plugins header prevents activation without Kjeks; this
        // one-liner is cheap insurance for the "active but API not loaded" edge.
        if ( ! function_exists( 'kjeks_register_integration' ) ) {
            return;
        }
        Plugin::instance()->boot();
    }
);
Registration hangs off the kjeks_register_integrations action, which only the core plugin fires — so even without the guard you can't fatal by calling kjeks_register_integration() when Kjeks is absent. Kjeks loads its API on init (priority 20 fires that hook), and booting on plugins_loaded keeps the ordering correct.

2 · Register your technology

This is the heart of every add-on. Hook the kjeks_register_integrations action and call kjeks_register_integration( $id, $args ). Kjeks blocks the scripts until the category is granted:

add_action( 'kjeks_register_integrations', function (): void {
    kjeks_register_integration(
        'google-tags',                    // unique integration id
        array(
            'category'    => 'analytics', // gate: runs only when this is granted
            'label'       => 'Google Tag Manager / Analytics',
            'src_scripts' => array(
                'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX',
            ),
            'inline'      => array(
                "gtag('js', new Date()); gtag('config', 'G-XXXXXXX');",
            ),
            // 'handles' => array( 'my-registered-handle' ), // or gate an already-enqueued script
        )
    );
} );

The arguments

KeyWhat it does
categoryThe gate: preferences, analytics, or marketing. Unknown or necessary falls back to marketing.
labelHuman name shown in the cookie declaration.
src_scriptsExternal scripts to inject after consent (a URL string, or { src, attrs }).
inlineInline JS snippets to run after consent.
handlesRegistered script handles to hold back — gate a script another plugin/theme enqueued.
Prefer one-liners? The core also exposes kjeks_enqueue_script(), kjeks_add_inline_script(), and kjeks_embed() — see the core 201 guide.

3 · React to consent on the front end

Registration is enough for most add-ons — Kjeks loads your scripts when consent is granted. But some technologies (like Google Consent Mode) must also signal on every change. Kjeks gives you a small JS surface: window.kjeks.isGranted( category ) and the kjeks:granted / kjeks:withdrawn events.

// Consent Mode v2 defaults — printed in wp_head BEFORE anything Google loads.
gtag( 'consent', 'default', {
    ad_storage: 'denied', ad_user_data: 'denied',
    ad_personalization: 'denied', analytics_storage: 'denied',
    wait_for_update: 500,
} );

// Sync to Kjeks now and on every change.
function granted( c ) {
    return window.kjeks ? window.kjeks.isGranted( c ) : false;
}
function apply() {
    if ( typeof gtag !== 'function' ) return;
    gtag( 'consent', 'update', {
        analytics_storage: granted( 'analytics' ) ? 'granted' : 'denied',
        ad_storage:        granted( 'marketing' ) ? 'granted' : 'denied',
    } );
}
window.addEventListener( 'kjeks:granted', apply );
window.addEventListener( 'kjeks:withdrawn', apply );
document.addEventListener( 'DOMContentLoaded', apply );

kjeks-google prints the first block (defaults denied) in wp_head at priority 1, and the sync block at priority 2 — so Google's own state is denied until Kjeks says otherwise, a second layer on top of the gate.

4 · Settings, config & updates

Store configuration with AddonKit

The core plugin ships a small set of shared base classes under Soderlind\Kjeks\AddonKit so add-ons don't re-implement settings plumbing. Extend AddonKit\SettingsPage and implement a handful of methods — the base handles reading/writing the option (network vs regular), nonces, and mounting your screen as a submenu of the core “Cookie Consent” menu on both single-site and Multisite. The kjeks-scripting add-on is the reference example:

use Soderlind\Kjeks\AddonKit\Categories;
use Soderlind\Kjeks\AddonKit\SettingsPage;

final class Settings extends SettingsPage {
    protected function option_key(): string { return 'kjeks_scripting'; }
    protected function menu_slug(): string  { return 'kjeks-scripting'; }
    protected function page_title(): string { return __( 'Kjeks Scripting', 'kjeks-scripting' ); }
    protected function menu_title(): string { return __( 'Scripting', 'kjeks-scripting' ); }

    protected function defaults(): array { return ScriptRules::defaults(); }
    protected function normalize( array $raw ): array { return ScriptRules::normalize( $raw ); }

    protected function render_fields( string $prefix, array $config ): void {
        // Echo the form rows. Build field names with $this->field_name( $prefix, ... )
        // so they work on both the site and network forms, and render the category
        // picker with Categories::render_select( $name, $id, $current ).
    }
}
ClassRole
AddonKit\PluginSingleton bootstrap — extend it and implement register() to wire your hooks.
AddonKit\SettingsPageAbstract settings screen that mounts under the core menu. Implement option_key(), menu_slug(), page_title(), defaults(), normalize(), render_fields() (plus optional menu_title()/parent_slug()).
AddonKit\OptionsReads/writes the option, network-aware.
AddonKit\CategoriesConsent-category picker: choices(), is_valid(), coerce(), render_select().
Guard against the core plugin being active-but-not-loaded by checking class_exists( \Soderlind\Kjeks\AddonKit\Plugin::class ) before extending. In your add-on's own tests, don't require the core vendor/autoload.php — register a targeted spl_autoload_register for the AddonKit namespace instead.

Offer a filter

Let other code adjust your resolved config at runtime:

add_filter( 'kjeks_google_config', function ( $config, $blog_id ) {
    // $config = [ 'gtm_id' => '', 'ga4_id' => 'G-XXXX', 'gating_category' => 'analytics' ]
    return $config;
}, 10, 2 );

Ship self-updates (optional)

Add soderlind/wordpress-github-updater via Composer and call GitHubUpdater::init() in your main file, exactly like the rest of the family — see any add-on's bootstrap for the pattern.

Checklist: ① Requires Plugins: kjeks header · ② one-line function_exists() / class_exists() guard · ③ register on kjeks_register_integrations · ④ (if needed) sync via kjeks:granted/kjeks:withdrawn · ⑤ settings via AddonKit\SettingsPage + a config filter · ⑥ verify with the scanner that nothing loads before consent.

Study the full manual example in the kjeks-google repository, the AddonKit example in kjeks-scripting, and see the core developer guide for the complete integration API.