Title: Tinker Stats for Rybbit
Author: TinkerCoded
Published: <strong>May 10, 2026</strong>
Last modified: May 10, 2026

---

Search plugins

![](https://ps.w.org/tinker-stats-for-rybbit/assets/banner-772x250.png?rev=3527868)

![](https://ps.w.org/tinker-stats-for-rybbit/assets/icon-256x256.png?rev=3527868)

# Tinker Stats for Rybbit

 By [TinkerCoded](https://profiles.wordpress.org/tinkercoded/)

[Download](https://downloads.wordpress.org/plugin/tinker-stats-for-rybbit.1.4.1.3.zip)

 * [Details](https://wordpress.org/plugins/tinker-stats-for-rybbit/#description)
 * [Reviews](https://wordpress.org/plugins/tinker-stats-for-rybbit/#reviews)
 *  [Installation](https://wordpress.org/plugins/tinker-stats-for-rybbit/#installation)
 * [Development](https://wordpress.org/plugins/tinker-stats-for-rybbit/#developers)

 [Support](https://wordpress.org/support/plugin/tinker-stats-for-rybbit/)

## Description

Tinker Stats for Rybbit integrates your self-hosted [Rybbit Analytics](https://rybbit.io)
instance with WordPress. It manages your Rybbit tracking script on the frontend 
and surfaces content traffic stats directly in your admin; post list columns, editor
publish box, and dashboard widget.

**Tracking Script Management:**

 * **One-click setup** — enter your connection details, click Test Connection, and
   the tracking script is ready to enable.
 * **Built-in proxy** — bypass ad blockers with one toggle. The plugin serves the
   tracking script and forwards events through your own domain. No webserver config
   needed.
 * **Script injection** — adds the Rybbit tracking script to your frontend via `
   wp_enqueue_script()` with configurable script URL, debounce, and session replay.
 * **Role exclusion** — exclude specific WordPress roles (e.g. administrators, editors)
   from being tracked.
 * **User identification** — optionally send logged-in user ID, display name, and
   role to Rybbit via `rybbit.identify()`.
 * **Custom events** — fire per-type events (`post_view`, `page_view`, `archive_view`,`
   search_view`, etc.) with WordPress metadata as event properties.
 * **Error suppression** — suppress cross-origin “Script error.” messages from the
   tracking script. This is related to a bit of a bug in Rybbit error reporting 
   and merely cleans up session reports.

**Admin Content Stats Display:**

 * **Post list column** — see view counts for every post at a glance. Click any 
   count to open a detailed chart.
 * **Chart modal** — daily breakdown with Year/Month/Week tabs and period navigation.
   Shows total count with percentage change vs previous period.
 * **Dashboard widget** — site-wide traffic chart, total count with trend indicator,
   and top 10 pages.
 * **Publish box stat** — current month view count right in the editor, clickable
   to open the chart modal.
 * **Postmeta sync** — hourly cron job saves view counts to post meta for use by
   themes, page builders, and other plugins.
 * **Configurable metric** — choose between Pageviews, Visits (sessions), or Users.

**Security & Privacy:**

 * **Encrypted API key** — your Rybbit API key is stored encrypted in the database.
 * **Caching** — configurable transient caching to minimize API calls to your Rybbit
   instance.
 * **API resilience** — automatic backoff on errors prevents cascading failures.
 * **View permissions** — restrict who can see stats in the admin.
 * **Privacy-first** — Rybbit is cookieless and GDPR-friendly. No data leaves your
   infrastructure.

This plugin is not an official Rybbit product and is not affiliated with or endorsed
by Rybbit. Rybbit trademarks, logos, and brand assets remain the property of their
respective owner.

**Requirements:**

 * A running self-hosted [Rybbit Analytics](https://rybbit.io) instance (tested 
   with v2.5.0)
 * A Rybbit API key (generated in Settings  Account  API Keys)
 * WordPress 6.3 or later

### Multisite

The plugin works on WordPress Multisite networks (subdirectory, subdomain, and domain-
mapped). The plugin can be activated per-subsite or network-wide; either works. 
Each subsite has its own Tinker Stats menu and runs its own postmeta sync.

How independently each subsite can be configured depends on Rybbit’s per-domain 
data model. Subdomain and domain-mapped subsites can each connect to their own Rybbit
site. Subdirectory subsites must share the parent domain’s Rybbit configuration (
Site ID, Tracking ID, URL, and API key), since Rybbit organizes data per registered
domain or subdomain rather than by URL path. The URL patterns section below has 
the full breakdown.

#### Rybbit + Multisite URL patterns

Rybbit organizes data per registered domain or subdomain, which affects how multisite
installs map to Rybbit sites:

 * **Subdomain subsites** (e.g. `sub.example.com`) — each can be added as its own
   Rybbit site for clean separation.
 * **Domain-mapped subsites** (e.g. `example2.com`) — each is added as its own Rybbit
   site, identical to single-site usage.
 * **Subdirectory subsites** (e.g. `example.com/sub`) — Rybbit doesn’t support path-
   based site separation, so all subdirectory subsites must be configured with the
   same Rybbit Site ID, Tracking ID, URL, and API key as the parent domain. In Rybbit’s
   dashboard, all subdirectory subsite traffic appears under the parent domain’s
   site; use Rybbit’s URL/path filters to view per-subsite breakdowns.

If you proxy the Rybbit script through a relative path (e.g. `/api/script.js`), 
make sure the proxy is configured on each subsite’s domain.

## Screenshots

 * [[
 * Post list with Visits column — click any count to open the chart modal
 * [[
 * Chart modal — daily breakdown with Year/Month/Week tabs and period navigation
 * [[
 * Dashboard widget — site-wide traffic chart with top pages
 * [[
 * Publish box — current month view count in the editor
 * [[
 * Settings page — Connection, Tracking, Display, and Tools sections

## Installation

 1. Upload the `tinker-stats-for-rybbit` folder to `/wp-content/plugins/`
 2. Activate the plugin through the ‘Plugins’ menu
 3. Go to Tinker Stats in the admin menu (or you’ll be redirected automatically on 
    first activation)
 4. Enter your Rybbit instance URL, Site ID, and API key
 5. Click “Test Connection” to verify and auto-populate the Tracking ID
 6. Enable tracking in the Tracking section to inject the script on your frontend
 7. Choose your preferred display metric (Pageviews, Visits, or Users)

## FAQ

### What is Rybbit?

Rybbit is an open-source, privacy-friendly web analytics platform — a modern alternative
to Google Analytics. It supports self-hosting and tracks visitors without cookies.

### Does this plugin add tracking to my site?

Yes, if you enable it. The Tracking section lets you inject the Rybbit tracking 
script on your frontend with one toggle. You can also configure role exclusion, 
user identification, and custom events. If tracking is disabled, the plugin only
reads existing analytics data from your Rybbit instance via its API.

### What does the postmeta sync do?

Every hour, the plugin fetches lifetime cumulative metrics from Rybbit and saves
them as post meta (`_tinker_stats_views`). This makes view counts available to themes,
page builders, and sorting queries.

The stored value is a running total from the date your Rybbit site was first created—
it doesn’t reset at month boundaries. The plugin auto-detects this start date from
your Rybbit instance the first time it syncs; if the connection details change (
different Rybbit instance or different Site ID), the start date is re-detected on
the next sync.

### Can I trigger a manual sync?

Yes. Go to Tinker Stats  Settings and click “Sync Postmeta Now” in the Tools section.

### Is my API key stored securely?

Yes. The API key is encrypted using AES-256-CBC with keys derived from your WordPress
salts before being stored in the database.

### My visits aren’t being counted — what’s wrong?

The most common cause is ad blockers (uBlock Origin, Brave’s built-in blocker, etc.)
blocking the Rybbit tracking script. Even though Rybbit is privacy-friendly and 
cookieless, some blocklist maintainers block all analytics scripts indiscriminately.
Estimates put the impact between 6% and 60% of visitors depending on your audience’s
technical level.

A common gotcha: ad blocker filter lists also match on URL keywords. Words like “
stats”, “analytics”, “tracking”, “metrics”, or “telemetry” anywhere in the script
URL will trigger blockers regardless of the actual script content. If your Script
URL contains any of these, expect blocking even before the request reaches your 
server.

The fix is to proxy the tracking script through your own domain so it loads as a
first-party request. The plugin includes a **built-in proxy** that handles this 
for you: go to Tinker Stats  Settings  Tracking and enable the “Bypass Ad Blockers”
toggle. The plugin will fetch and cache the Rybbit script, generate a randomized
first-party path, and forward all tracking events through your own domain. No webserver
config required.

If you’d rather configure the proxy yourself at the webserver or CDN level, see 
[Rybbit’s proxy guide](https://rybbit.com/docs/proxy-guide/get-started) and enter
the relative path (e.g. `/api/script.js`) in the Script URL field.

To verify your tracking is actually firing, open your Rybbit dashboard, go to Site
Settings  Tracking Script, and click “Verify Installation”. Rybbit will check whether
the script is loading on your site and confirm the connection.

Other less common causes: tracking is disabled in the plugin settings, your role
is in the Exclude Roles list, or the tracking ID isn’t populated yet.

### How does the built-in proxy work?

When enabled, the plugin generates a randomized 8-character hex prefix (e.g. `k3p9m2qr`)
and serves the Rybbit tracking script and tracking endpoints through `https://yoursite.
com/[hash]/...`. Static script files are cached locally and refreshed daily; tracking
POSTs are forwarded to your Rybbit instance in real time, with the visitor’s real
IP address preserved via `X-Forwarded-For`.

The randomized prefix breaks ad blocker rules that anchor on the platform-pattern`/
api/` path used by analytics services. You can regenerate paths anytime, refresh
the script cache manually, and verify proxy reachability with the Test Proxy button.

The proxy works on most hosts without webserver-level changes. On WordPress Multisite,
each subsite gets its own proxy paths (subdirectory subsites can share the parent
domain’s Rybbit Site ID; subdomain and mapped-domain subsites have their own).

### I use page caching — do I need to do anything?

The tracking script is injected into your page HTML via `wp_enqueue_script()`, so
it becomes part of the cached HTML output. This is normally desirable: once cached,
the script tag is served on every page hit without any per-request PHP work.

The trade-off: if you enable, disable, or change tracking settings, your cached 
HTML still reflects the previous state. You’ll need to purge your page cache (FastCGI,
Cloudflare, Varnish, etc.) for the change to take effect on the frontend.

The built-in proxy’s cached script files (`/tsr-{hash}/script.js` etc.) are static
files at WordPress root, served directly by your webserver. They’re independent 
of WordPress page caching: they refresh on their own daily-cron schedule and aren’t
affected by page cache purges.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Tinker Stats for Rybbit” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ TinkerCoded ](https://profiles.wordpress.org/tinkercoded/)

[Translate “Tinker Stats for Rybbit” into your language.](https://translate.wordpress.org/projects/wp-plugins/tinker-stats-for-rybbit)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/tinker-stats-for-rybbit/),
check out the [SVN repository](https://plugins.svn.wordpress.org/tinker-stats-for-rybbit/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/tinker-stats-for-rybbit/)
by [RSS](https://plugins.trac.wordpress.org/log/tinker-stats-for-rybbit/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.4.1.3

 * Fixed: postmeta sync was overwriting `_tinker_stats_views` with current-calendar-
   month-only data on each hourly run, causing values to collapse at month boundaries.
   The cron now queries lifetime cumulative metrics — start date is auto-detected
   from your Rybbit instance’s site creation date and cached, with sanity bounds
   and re-detection when connection details change.
 * Refactored: filesystem operations across the proxy cache manager, MU-plugin deployer,
   and uninstall cleanup migrated to the WP_Filesystem API for WordPress.org coding-
   standards compliance.
 * Refactored: MU-plugin tracking forwarder migrated from raw cURL to `wp_remote_request()`.
   Same timeout, SSL verification, and request shape as before; this is a coding-
   standards refactor, not a behavior change.
 * Hardened: `$_SERVER` reads in the deployed MU-plugin now properly unslashed and
   sanitized.
 * Note: removed the redundant `Network: false` plugin header line — single-site
   is the implicit default and only `Network: true` is a valid value per WordPress.
   org spec.

#### 1.4.1.1

 * Added: MU-plugin fast path for proxy requests. When the proxy is enabled, the
   plugin auto-deploys a small MU-plugin to `wp-content/mu-plugins/tinker-stats-
   proxy.php` that handles forwarded `/track` and `/site/tracking-config/` requests
   directly, bypassing the full WordPress bootstrap. Works on single-site and WordPress
   Multisite (each subsite gets its own per-host config sidecar). Falls back automatically
   to the existing PHP handler if the mu-plugins directory isn’t writable; the proxy
   works in either mode.
 * Added: Fast path status row in the proxy fieldset on the Settings page so you
   can see at a glance whether the MU-plugin path is active or PHP fallback is in
   use.
 * Added: optional proxy debug logger for diagnosing visitor-IP attribution issues.
   Enable by adding `define( 'TINKER_STATS_PROXY_DEBUG', true );` to `wp-config.
   php`. When enabled, each forwarded tracking request writes one line to the PHP
   error log capturing the inbound IP-related `$_SERVER` values, the IP the proxy
   resolved as the visitor’s, and the exact `X-Real-IP` / `X-Forwarded-*` headers
   sent to Rybbit. Off by default; safe to leave in production.
 * Added: page title metadata to the `home_view` event — homepage hits now carry
   the page title alongside other custom-event metadata.
 * Added: pre-flight check in the proxy enable flow that verifies WordPress root
   is writable by PHP. If it isn’t (some hosts lock the document root), enable surfaces
   a clear error and points to Rybbit’s external proxy guide as the alternative.
 * Fixed: PHP warnings (“Attempt to read property on false”) on archive pages where`
   get_queried_object()` failed to resolve — most commonly bot or invalid URL hits
   to `/author/{slug}/` for slugs that don’t match any user. The tracking script
   now defensively type-checks the queried object on singular, term, and author 
   archive branches before reading properties. Unresolvable archives no longer fire
   tracking events with garbage metadata.
 * Fixed: visitor IP attribution through the built-in proxy. The plugin now injects
   the resolved visitor IP into the request body of forwarded `/track` events. Rybbit’s
   track endpoint honors this body field directly, bypassing any Cloudflare or nginx
   layer between the WordPress server and the Rybbit backend that may otherwise 
   overwrite reverse-proxy headers like `X-Real-IP`. Pageviews, custom events, errors,
   and outbound clicks now record the real visitor IP regardless of the Rybbit deployment
   topology. (Note: Rybbit’s `/identify` and session-replay endpoints don’t currently
   support this override pattern; an upstream Rybbit change would close that gap.)
 * Fixed: contradicting positioning language. Plugin description and readme requirements
   now consistently describe Tinker Stats for Rybbit as integration with self-hosted
   Rybbit instances.
 * Fixed: built-in proxy was forwarding tracking events with only `X-Forwarded-For`,
   causing Rybbit to record the WordPress server’s IP instead of the real visitor
   IP on setups that use `X-Real-IP` for client identification. Proxy now sets `
   X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`, and `X-Forwarded-Host`, matching
   the standard reverse-proxy header pattern.
 * Fixed: homepage events with custom event tracking enabled were silently dropped
   on some self-hosted Rybbit instances. The `home_view` event was sending an empty
   properties array (`[]`) instead of an empty object (`{}`), which Rybbit’s analytics
   database rejects. Some setups dropped only the `home_view` row; others dropped
   the homepage pageview alongside it.
 * Fixed: built-in proxy returned 404 on managed hosts (xCloud, WP Engine, Kinsta,
   etc.) where nginx has a static-asset location block that returns 404 directly
   for missing .js files instead of falling through to PHP. Cached tracking script
   files now live at WordPress root in a `tsr-{hash}/` directory so nginx serves
   them from disk as static assets. Non-static endpoints (`/track`, `/site/tracking-
   config/...`, etc.) continue to route through PHP under the same prefix.
 * Refactored: outbound request logic factored into a shared helpers class so the
   MU-plugin fast path and PHP fallback produce byte-identical requests to Rybbit.
   Same visitor IP attribution, same headers, same body-field injection in both 
   paths.
 * Hardened: event property serialization now forces JSON object output (`JSON_FORCE_OBJECT`)
   so any future event with empty metadata can’t reproduce the same failure mode.
 * Note: if you had v1.4.0’s proxy enabled, disable and re-enable after upgrading—
   the cache directory has moved.

#### 1.4.0

 * Added: built-in proxy feature — bypass ad blockers with one toggle. Plugin serves
   the Rybbit tracking script and forwards tracking events through your own domain
   via a randomized first-party path (e.g. `/k3p9m2qr/script.js`).
 * Added: Settings  Tracking  “Bypass Ad Blockers” fieldset with proxy enable/disable,
   paths display, copy-to-clipboard, Refresh Now, Regenerate Paths, and Test Proxy
   actions.
 * Added: daily cron refresh of cached `script.js`, `replay.js`, and `metrics.js`
   from your Rybbit instance. Filterable via `tinker_stats_proxy_refresh_interval`.
 * Added: tracking POST forwarding (`/[hash]/track`) preserves real visitor IP via`
   X-Forwarded-For` (Cloudflare-aware).
 * Added: catch-all forwarder for any future Rybbit endpoint under `/api/*` — proxy
   keeps working without plugin update if Rybbit adds new endpoints.
 * Added: cache directory at `/wp-content/uploads/tinker-stats-proxy-cache/` survives
   plugin updates.
 * Added: hash regeneration via Regenerate Paths button — cache files preserved (
   URL-independent shared dir).
 * Note: this version uses WordPress’s `parse_request` for routing, which works 
   on all single-site and multisite installs. A faster MU-plugin path for single-
   site installs is planned for a future release.

#### 1.3.2

 * Added: multisite compatibility — network activation now installs defaults and
   schedules cron on every existing subsite. New subsites added to a network where
   the plugin is network-active automatically get defaults and cron set up.
 * Added: lazy cron-schedule fallback ensures the postmeta sync runs on every subsite
   even if the activation hook missed it.
 * Added: multisite-aware uninstall — cleanup runs on every subsite, no more orphaned
   options or postmeta.
 * Added: activation redirect — after activating on a single site or per-subsite,
   you’re sent straight to the Settings page.
 * Added: Settings moved to a top-level menu (Tinker Stats) in preparation for future
   Events and Stats submenu pages.
 * Added: contextual hint next to Site ID field on multisite subsites, explaining
   how Rybbit’s domain-based site organization maps to subdirectory/subdomain/mapped
   subsites.
 * Added: contextual hint next to Script URL field about ad blockers, with a link
   to Rybbit’s proxy guide.
 * Fixed: “Settings saved” notice not appearing after saving — top-level menus require
   an explicit notice render call.
 * Fixed: top-level menu position moved into the plugin group (below the separator
   after Settings) instead of squeezed between Settings and Tools.
 * Documentation: expanded Multisite section in readme with explicit Rybbit URL 
   pattern compatibility.
 * Documentation: new FAQ entry for “My visits aren’t being counted” addressing 
   ad blockers.

#### 1.3.0

 * Initial release.

## Meta

 *  Version **1.4.1.3**
 *  Last updated **1 day ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.3 or higher **
 *  Tested up to **6.9.4**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [analytics](https://wordpress.org/plugins/tags/analytics/)[pageviews](https://wordpress.org/plugins/tags/pageviews/)
   [privacy](https://wordpress.org/plugins/tags/privacy/)[rybbit](https://wordpress.org/plugins/tags/rybbit/)
   [statistics](https://wordpress.org/plugins/tags/statistics/)
 *  [Advanced View](https://wordpress.org/plugins/tinker-stats-for-rybbit/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/tinker-stats-for-rybbit/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/tinker-stats-for-rybbit/reviews/)

## Contributors

 *   [ TinkerCoded ](https://profiles.wordpress.org/tinkercoded/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/tinker-stats-for-rybbit/)