BOL Split Testing
A/B split testing tool to randomly route visitors between the WordPress homepage and the payments app, tracking results.
by George Stephanis / Big Orange Lab · github.com/bigorangelab/bol-split-testing
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/bigorangelab/bol-split-testing/archive/refs/heads/trunk.zip
BOL Split Testing
A single-file WordPress plugin that A/B tests the site homepage against an external payments app, splitting visitors by a configurable percentage and tracking display/redirect counts.
What it does
Visitors landing on the homepage are randomly assigned to one of two groups:
- Group A (Displays) — stays on the WordPress homepage.
- Group B (Redirects) — gets redirected (HTTP 307) to a configured payments app URL (e.g.
https://payments.example.com).
The assignment is decided once per visitor and pinned for 7 days via the bol_landing_variant cookie, so a given visitor always sees the same variant for the duration of the test. Search engine crawlers and logged-in administrators are always excluded from the test and simply see the homepage.
Routing methods
The plugin supports two mutually exclusive routing strategies, selectable from the settings page:
- Client-side (JS Hybrid) — recommended. An inline script in
wp_headdecides the variant in the browser and redirects viawindow.location.replace(). This works transparently with WP Engine's server-side page caching, since the cached PHP response never changes — only the browser branches. - Server-side (PHP).
template_redirectdecides and redirects server-side. This requires a WP Engine cache exclusion rule for/— without it, cached responses will serve stale redirect decisions to visitors.
Both methods share the same decision logic (bypass checks, cookie name/lifetime, percentage roll) implemented independently in PHP and JS.
Decision flow
Both routing methods walk through the same sequence of checks before ever touching the roll or the cookie. The PHP path (bol_server_side_redirect()) runs this on every request via template_redirect; the JS path (bol_client_side_script() + assets/js/frontend.js) runs the bypass checks in PHP at enqueue time (so bypassed visitors never even receive the script), then re-derives the rest in the browser against a page that may be cached indefinitely.
flowchart TD
A[Request to front-end homepage] --> B{Is test active?<br/>bol_is_test_active}
B -- No --> Z[Show normal homepage<br/>no cookie, no tracking]
B -- Yes --> C{Bypass query param<br/>present? e.g. ?bypass_ab=1}
C -- Yes --> Z
C -- No --> D{Privileged role bypass?<br/>bol_should_bypass_for_role<br/>Editor+, or Contributor+ if enabled<br/>skipped entirely at 100%}
D -- Yes --> Z
D -- No --> E{Known search bot UA?<br/>bol_is_search_bot}
E -- Yes --> Z
E -- No --> F{bol_landing_variant<br/>cookie already set?}
F -- "redirect" --> G[307 redirect to target URL<br/>no re-roll, no re-count]
F -- "keep" --> H[Show homepage<br/>no re-roll, no re-count]
F -- Not set --> I[Roll: random 0-99 < percentage?]
I -- Yes --> J[Set cookie = redirect, 7 days<br/>increment bol_count_redirects<br/>307 redirect to target URL]
I -- No --> K[Set cookie = keep, 7 days<br/>increment bol_count_displays<br/>show homepage]
subgraph PHP["Server-side PHP method"]
direction TB
P1[Everything above runs inline<br/>on template_redirect, every request]
end
subgraph JS["JS Hybrid method"]
direction TB
J1[B–E run in PHP at wp_enqueue_scripts<br/>bypassed visitors never get the script]
J2[bolFrontendData localized:<br/>percentage, target/track/redirect URLs, schedule]
J3[frontend.js re-checks schedule<br/>if Conditionally Enabled, then F-K<br/>in the browser, against a cached page]
J4[keep: fetch POST /track<br/>redirect: navigate to<br/>/redirect-and-track uncached REST route]
J1 --> J2 --> J3 --> J4
end
A few things the diagram compresses:
- "Is test active?" folds in the tri-state
bol_statusoption and, for "Conditionally Enabled," the full schedule check (date range, day-of-week, time-of-day window, evaluated in the site's timezone). For the JS Hybrid method this check is re-run in the browser on every pageview (seeisScheduleActive()inassets/js/frontend.js), because the enqueued script itself lives on a page that may be served from cache — PHP can't be the one deciding "is now inside the window" once and bake that into the cached HTML. - Bypassed visitors are never counted — the display/redirect tallies only increment inside the roll step (
I), so returning early at any ofB–Eskips tracking entirely. - The cookie is the only state that persists a decision. There's no server-side lookup keyed by IP or user — once
bol_landing_variantis set toredirectorkeep, that decision short-circuits everything downstream ofFfor the next 7 days, in both PHP and JS. - PHP and JS intentionally duplicate this logic rather than sharing code, since one runs server-side per-request and the other runs client-side against a cached response — see
CLAUDE.mdfor why, and keep both in sync when changing the rules.
Settings
Configurable from Tools → BOL Split Testing in wp-admin:
| Option | Description |
|---|---|
| Enable Split Test | Tri-state: Disabled, Enabled, or Conditionally Enabled. |
| Schedule (Conditionally Enabled only) | Start/end date-time, active days of the week, and an active time-of-day window (site timezone) — see below. |
| Redirect Target URL | Destination URL for Group B. |
| Redirect Split Percentage | Percent (0–100) of visitors redirected to the target. |
| Routing Method | js_hybrid (default) or php. |
| Bypass Query Parameter | Query key (default bypass_ab) that skips the test entirely, e.g. /?bypass_ab=1. |
| Exclude Logged-In Users | Checkbox. Editors and above are always bypassed (never redirected/counted, shown an admin-bar link instead); enabling this extends that bypass down to Contributors and Authors. Ignored — everyone is redirected — when Redirect Split Percentage is 100. |
The same page displays running totals of displays vs. redirects, with a button to reset the counters.
Conditional scheduling
When Enable Split Test is set to "Conditionally Enabled," the test only runs during a configured recurring window: an optional start/end date-time, a set of active days of the week, and an active time-of-day range (a start time later than the end time is treated as spanning midnight). All schedule times are evaluated in the site's configured timezone, which is restated on the settings page; the admin UI also flags if your browser's timezone differs from the site's, since schedule times are not adjusted for the viewer. A read-only weekly calendar preview on the settings page visualizes the configured active windows across all 7 days.
Because the recommended JS Hybrid routing method relies on a page-cached `