Kjeks
← All docs

Kjeks Scanner

A standalone Node + Playwright scanner. Crawls your pages under every consent state and reports the trackers that actually fire.

Standalone Node 20+ CLIUses PlaywrightFeeds the Kjeks core inventorySource on GitHub →

101 First scan

The scanner runs outside WordPress. It drives a real Chromium browser through each consent state and records the network requests, cookies, and storage that appear — catching trackers injected by themes, embeds, or other plugins that are easy to miss by hand.

# Install (Node 20+)
npm install
npx playwright install chromium

# Scan a single URL
node src/cli.js --url https://example.com/ --blog-id 1 --out scan
# or, once installed as a package:
kjeks-scan --url https://example.com/ --blog-id 1 --out scan

The six consent states

Every path is loaded once per state, each in a fresh browser context:

StateConsent injected
before-choiceNone — the banner hasn't been answered.
reject-allEverything optional denied.
only-preferencesOnly preferences granted.
only-analyticsOnly analytics granted.
only-marketingOnly marketing granted.
accept-allAll optional categories granted.
For states with a choice, the scanner writes the kjeks_consent cookie and localStorage value before page scripts run — so blocking is evaluated exactly as a returning visitor would experience it.

201 Config & authentication

Config file

For anything beyond a single URL, describe your sites in a config file and pass--config. Each site lists the paths to crawl and optionalscenarios (scripted click/wait steps for gated content like video embeds):

{
  "sites": [
    {
      "url": "https://example.com/",
      "blog_id": 1,
      "policy_version": 1,
      "paths": [ "/", "/about" ],
      "scenarios": [
        {
          "name": "open a gated video",
          "steps": [
            { "action": "click", "selector": ".kjeks-embed__load" },
            { "action": "wait", "ms": 1000 }
          ]
        }
      ]
    }
  ]
}

Generate it from WordPress

Don't hand-write the site list — the core plugin can emit it. Use the CLI, or pull it from the REST route with credentials:

# Generate the config from WordPress, then scan it
wp kjeks scan-config --output=config.json
node src/cli.js --config config.json --out scan

# Pull config straight from a live site (HTTP Basic auth)
KJEKS_USER=admin KJEKS_APP_PASSWORD='xxxx xxxx …' \
  node src/cli.js --config-url https://example.com/wp-json/kjeks/v1/scan-config --out scan

Invocation forms

FlagPurpose
--url + --blog-idScan one URL without a config file.
--config <file>Scan sites from a local config file.
--config-url <url>Fetch the config from a live site (needs KJEKS_USER + KJEKS_APP_PASSWORD).
--overlay <file>Merge extra paths/scenarios by blog_id.
--concurrency <n>Sites scanned in parallel (default 3).
--per-host <n>Parallel scans sharing one hostname (default 2) — politeness for subdirectory multisites.
--fullScan the server selection as-is; skip re-scanning pages that previously produced a tracker.
--import [<url>]After scanning, POST observations to the import endpoint in the same run.
--out <dir>Output directory (default scan).
--endpoint <CDP>Connect to an existing browser over the DevTools protocol.

301 Output & CI

What it writes

One stable JSON file per site is written to <out>/<host>[_<path>].json. Each file records every state plus a flattened list of observations:

{
  "host": "example.com",
  "url": "https://example.com/",
  "blog_id": 1,
  "states": {
    "before-choice": { "cookies": [], "scripts": [], "iframes": [], /* … */ },
    "reject-all":     { /* … */ },
    "accept-all":     { /* … */ }
  },
  "observations": [
    { "name": "_ga", "storage_type": "cookie", "party": "third",
      "domain": ".example.com", "retention": "…",
      "triggered_by": ["accept-all", "only-analytics"],
      "source_urls": ["/", "/blog/hello-world/"] }
  ]
}

Per state, the scanner captures:

cookies, localStorage, sessionStorage,indexedDB, thirdPartyHosts, beacons,scripts, iframes, and redirects. Each observation also records triggered_by — the consent state that caused it — and source_urls, the page(s) it actually loaded on, so you can see exactly which choice and which page produced a tracker. (The kjeks_consentcookie itself is excluded from results.)

Targeted re-scans. The core plugin auto-selects representative URLs per site (home, newest post/page, embed-bearing pages, capped). On each run the scanner also re-scans every page that previously produced a tracker (fromsource_urls), so sampling never drops a known-tracker page. Pass--full to scan the server selection as-is.

Diffing & exit codes

On each run the scanner compares the new file with the previous one andexits with status 1 when anything changed. That makes it a natural CI gate: a non-zero exit means a new tracker appeared and your inventory needs review.

Close the loop

Once you've reviewed the results (optionally with theAI Reviewer), import them back into WordPress — either as a separate step, or in the same run with --import:

# Feed reviewed observations back into the inventory
wp kjeks import scan/example.com.json --blog_id=1
The scanner has no WordPress hooks or options — it integrates purely through the core plugin's scan-config and import surfaces. See thecore 301 guide for those.