Privacy-friendly, cookieless, no-PII page-view analytics for WordPress — self-hosted, multisite-safe, no third-party service.
inklura-analytics records aggregate page views in a small per-blog table and shows them on a clean
operator dashboard. There are no cookies, no IP storage, and no personal data — just a
normalized request path, a day, and a count. It honors Do Not Track, skips configured user roles,
and degrades gracefully (a <noscript> pixel fallback). Everything stays on your own server.
| Area | What it does |
|---|---|
| Tracking | A tiny inline beacon (navigator.sendBeacon, fetch keepalive fallback) POSTs the current path + referrer host to a REST route. A <noscript> 1×1 pixel falls back to admin-ajax. Cookieless, no IP, no PII. |
| Privacy | Honors the DNT / Sec-GPC header, skips logged-in users in an excluded role (default: administrators), never stores IPs or user identifiers. The endpoint re-applies the gate server-side. |
| Referrers | Stores the referrer HOST only (never the full URL or query — no PII), self-referrals dropped, with a Top referrers table. |
| Devices | A coarse desktop / mobile / tablet class from the User-Agent without fingerprinting (well-known platform tokens only), with a breakdown on the dashboard. |
| Bot filtering | Known crawlers / automation / HTTP libraries (and empty User-Agents) are skipped at the endpoint via a conservative UA heuristic. |
| Storage | Per-blog custom tables created via dbDelta: aggregate hits (one row per day + path + device, INSERT … ON DUPLICATE KEY UPDATE), per-day referrer hosts, and a tiny rolling "live" pings table (timestamps only). Paths are normalized (query stripped, slashes collapsed, capped at 190 chars). |
| Dashboard | A date-range selector (today / 7d / 30d / custom), a live last-30-minutes count, views-today + range-total KPI cards, a per-day bar chart, a device breakdown, and top-pages + top-referrers tables. CSV export of the selected range. Server-rendered and escaped. |
| Settings | Enable/disable tracking, choose excluded roles, set retention days, toggle DNT respect, and excluded paths (prefix / glob patterns skipped at the endpoint). |
| Retention | A daily wp-cron event prunes rows older than the retention window (hits + referrers; live pings always roll off). Manual "prune now" button too. |
| Multisite | Per-blog stats (tables per blog), created on activation and when a new site is created (wp_initialize_site). Network-admin defaults page (incl. excluded paths). Uninstall drops every blog's tables. |
| Auto-update | Self-hosted updates for normal-plugin installs — "update available" + one-click update from a manifest, like a wordpress.org plugin (must-use installs update out of band). |
- No cookies — the beacon stores nothing on the client.
- No IP storage — the server never persists the visitor IP.
- No PII — only aggregates are stored:
day + path + device + count, per-day referrer hosts (never full URLs), and live timestamps (no path). - Referrer = host only — the path/query of the referrer is never stored; self-referrals are dropped.
- Device = coarse bucket — desktop / mobile / tablet from the User-Agent, no fingerprinting (no version, OS build, screen, canvas or font probing).
- Bot filtering — known crawlers and empty User-Agents are skipped at the endpoint.
- Do Not Track — visitors sending
DNT: 1(orSec-GPC: 1) are not counted (configurable). - Role exclusion — logged-in users with an excluded role are skipped (default: administrators).
Put the repository in wp-content/plugins/inklura-analytics/ and activate Inklura Analytics.
The table is created on activation (on every blog when network-activated).
Drop the package under wp-content/mu-plugins/inklura-analytics/ and load
inklura-analytics.php from a small mu-loader. The table is created lazily on first run; the
auto-updater is disabled for mu installs.
Open Analytics → Settings in wp-admin:
- Tracking — enable / disable recording.
- Do Not Track — honor the
DNTheader (on by default). - Excluded roles — roles whose views are not counted (administrators by default).
- Retention (days) — how long aggregated rows are kept (pruned daily).
- Excluded paths — one pattern per line; matched requests are skipped at the endpoint:
- a trailing
*is a prefix match —/cart*skips/cartand/cart/anything; - a bare path also matches its sub-paths —
/privateskips/privateand/private/x; */?elsewhere are glob-matched per path segment —/a/*/bskips/a/x/b(not/a/x/y/b).
- a trailing
The dashboard (Analytics → Overview) has a date-range selector (today / 7d / 30d / custom), a live last-30-minutes count, a per-day bar chart, a device breakdown, top pages, top referrers, and an Export CSV button for the selected range.
On multisite, Network Admin → Settings → Inklura Analytics sets defaults (incl. excluded paths) that each site can override.
define( 'INKLURA_ANALYTICS_ENABLE', false ); // disable the whole pluginPOST /wp-json/inklura-analytics/v1/hit { p: "/path", r: "referrer.host" } # beacon
GET /wp-admin/admin-ajax.php?action=inklura_analytics_hit&p=/path # noscript pixel
Both validate + normalize input, re-apply the privacy gate (DNT, excluded role, bot filter,
excluded paths), derive the coarse device class + referrer host server-side, and write via
$wpdb->prepare. The dashboard CSV export is a separate nonce-guarded admin-post action.
| Hook | Type | Purpose |
|---|---|---|
inklura/inklura-analytics/should_track |
filter (bool) | Final say on whether to track the current request. |
inklura/inklura-analytics/record_path |
filter (string) | Adjust/skip the path right before it is recorded ('' to skip). |
inklura/inklura-analytics/update_manifest_url |
filter (string) | Override the update manifest URL. |
inklura/inklura-analytics/booted |
action | Fires after all modules boot. |
for t in tests/unit/*.php; do php "$t"; done # WP-free unit testsThe unit tests are WP-free (the bootstrap stubs the handful of WP functions used, and an in-memory
$wpdb fake exercises the record → aggregate path). They cover path normalization, referrer-host
extraction, device classification, bot detection, date-range math, excluded-path matching, top-N
aggregation, CSV building and the updater. Each file prints all passed.
CI lints on PHP 7.4 / 8.1 / 8.3, runs the unit tests, and builds the installable ZIP.
bin/build-zip.sh /tmp/inklura-analytics.zip # top-level folder: inklura-analytics/Inklura Analytics is built and maintained with the support of:
- webdesign29 — web agency (Brest, France).
- Inklura — WordPress-compatible CMS & hosting.
Interested in sponsoring? Use the Sponsor button or open an issue.
GPL-2.0-or-later — see LICENSE.