WP Manifestindependent plugin directory
manifest / analytics / greenshift-ab-testing

Greenshift A/B Variant Block

Lightweight, privacy-friendly A/B testing block for the WordPress block editor (FSE compatible).

by Greenshift · github.com/tquinonero/greenshift-ab-testing · website

★ 0stars
0forks

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/tquinonero/greenshift-ab-testing/archive/refs/heads/main.zip

A/B Variant Block for Greenshift

Lightweight, privacy-friendly A/B testing block for WordPress with Full Site Editing (FSE) support. No SaaS. No third-party scripts. Everything stays inside WordPress.


Table of Contents


How it works

Editor canvas                     Frontend
──────────────────────            ──────────────────────────────────────────
[A/B Variant]                     render.php
  └─ [Variant A] (your blocks)    reads cookie / IP hash → picks 0 or 1
  └─ [Variant B] (your blocks)    renders ONLY that child → <div data-gsab-…>
                                  ↓ wp_footer
                                  tracking.js (~500 bytes)
                                  → gsabTrack() via sendBeacon / fetch
                                  → POST /wp-json/greenshift/v1/ab-track
                                  → wp_gsab_events table
                                  ↓
                                  Tools → A/B Testing dashboard

Key point: only one variant's HTML is ever sent to the browser. There is no hidden DOM, no CSS toggling, no layout shift.


Features

  • Server-side rendering — one variant rendered per request, zero hidden markup
  • Cookie-based assignment — same visitor always sees the same variant
  • Deterministic IP-hash fallback — works even when cookies are blocked
  • Query-param override (?ab=0 / ?ab=1) — for testing and QA
  • Logged-in user bypass — show editors a fixed variant so they don't skew results
  • Configurable split ratio — any percentage, not just 50/50
  • Conversion tracking — click, form submit, URL visit, or custom JS event
  • REST tracking endpoint — nonce-verified, no external services
  • Custom DB table — all data stored locally in your WordPress database
  • Admin dashboard — stats table with z-score significance indicator and CSV export
  • Zero frontend footprint — when the block is not on a page, nothing loads
  • Four developer hooks — for full extensibility

Requirements

Requirement Minimum
WordPress 6.3+
PHP 7.4+
MySQL / MariaDB Any version supported by your WP install
Block editor Must be active (Classic Editor plugin will break this)
Node.js 18+ (only needed to rebuild JS from source)

Installation

Option A — Upload zip (recommended)

  1. Download or build greenshift-ab-testing.zip
  2. In wp-admin go to Plugins → Add New → Upload Plugin
  3. Upload the zip and click Activate

The database table (wp_gsab_events) is created automatically on activation.

Option B — Build from source

cd greenshift-ab-testing
npm install
npm run build

Then zip and upload, or symlink directly into wp-content/plugins/:

ln -s /path/to/greenshift-ab-testing /path/to/wp-content/plugins/greenshift-ab-testing
wp plugin activate greenshift-ab-testing

Development (watch mode)

npm start   # rebuilds automatically on file save

How to use the block

Step 1 — Insert the block

In the block editor click +, search for A/B Variant and insert it. You will see both variants side by side in the editor canvas:

┌─ A/B Variant Block ─────── A: 50% / B: 50% ─┐
│  ┌──── Variant A ────┐  ┌──── Variant B ────┐ │
│  │                   │  │                   │ │
│  │   (add blocks)    │  │   (add blocks)    │ │
│  │                   │  │                   │ │
│  └───────────────────┘  └───────────────────┘ │
└──────────────────────────────────────────────┘

Both variants are visible in the editor only so you can build and edit them. On the live site, each visitor sees exactly one.

Step 2 — Build your two variants

Click inside Variant A and add whatever blocks you want — headings, paragraphs, images, buttons, forms, etc. Do the same for Variant B with your alternative content.

Common use cases:

  • Two different CTA button texts ("Buy Now" vs "Get Started")
  • Two hero images or hero layouts
  • Long-form copy vs short-form copy
  • Testimonial block vs pricing table

Step 3 — Configure the split

Click the outer A/B Variant block to select it, then open the Inspector panel on the right.

  • Set Variant A traffic % — e.g. 50 for a 50/50 test, 80 if Variant B is a risky change
  • Choose a Conversion trigger (see Conversion triggers below)
  • Toggle Track impressions to record a view event on every page load (recommended on)

Step 4 — Publish

Publish the page normally. A unique block ID is auto-generated and saved with the post. That ID is how all stats are scoped.


Visitor assignment logic

Assignment is deterministic — the same visitor always sees the same variant. It is never random on a per-request basis.

Priority chain

1. Logged-in bypass     (if enabled in inspector)
        ↓
2. ?ab= query param     (if override enabled in inspector)
        ↓
3. Cookie               gs_ab_{block_id}  (set for 30 days)
        ↓
4. IP hash fallback     hash( IP + block_id + date ) → bucket 0–99

What each step means

Step Behaviour
Logged-in bypass All logged-in users see whichever variant you choose. Keeps editors from appearing in stats.
?ab= override ?ab=0 forces Variant A, ?ab=1 forces Variant B. Used for QA and demos.
Cookie Once assigned, a cookie locks the visitor to their variant for 30 days. Returning visitors always see the same version.
IP hash First-time visitors with no cookie are assigned by hashing their IP + block ID + today's date into a 0–99 bucket. If bucket < ratio → Variant A, otherwise → Variant B. The date component means the hash rotates daily, which helps avoid caching bias.

Who sees what — quick reference

Visitor type Gets
First visit, no cookie Assigned by IP hash based on your ratio
Returning visitor Same variant as before (from cookie)
?ab=0 in URL Always Variant A
?ab=1 in URL Always Variant B
Logged-in user (bypass on) Whatever you set in the inspector
Cookies blocked IP hash on every visit (still stable within the same day)

Testing your setup

Method 1 — Query parameter (recommended)

Add ?ab=0 or ?ab=1 to your page URL:

https://yoursite.com/your-page/?ab=0   ← see Variant A
https://yoursite.com/your-page/?ab=1   ← see Variant B

This overrides everything and lets you instantly switch between variants in the same browser session.

Method 2 — Check the rendered HTML

View the page source (Ctrl+U in your browser) and search for gsab-variant-wrap. You will find something like:

<div class="gsab-variant-wrap wp-block-greenshift-ab-variant"
     data-gsab-id="abc123"
     data-gsab-variant="0"
     ...>
  • data-gsab-variant="0" → Variant A was rendered
  • data-gsab-variant="1" → Variant B was rendered

Also verify that only one child block class appears in the source:

  • wp-block-greenshift-ab-variant-a → Variant A content is present
  • wp-block-greenshift-ab-variant-b → Variant B content is present

They should never both appear in the same page response.

Method 3 — Inspect the cookie

Open browser DevTools → Application → Cookies → your domain.

Look for gs_ab_XXXXXXXXXX. Its value is 0 (Variant A) or 1 (Variant B). Delete it and reload to get reassigned.

Method 4 — Check via curl (server-level confirmation)

# Should show: data-gsab-variant="0"
curl -sk "https://yoursite.com/your-page/?ab=0" | grep 'data-gsab-variant'

# Should show: data-gsab-variant="1"
curl -sk "https://yoursite.com/your-page/?ab=1" | grep 'data-gsab-variant'

Stats dashboard

Go to Tools → A/B Testing in wp-admin.

Column Description
Block ID Unique identifier for each A/B block on your site
Impr. A / B How many visitors saw each variant
Conv. A / B How many triggered your conversion event
CR A / B Conversion rate — conversions ÷ impressions (%)
Significance Statistical confidence using z-score

Significance levels

Label Meaning
≥95% confidence Strong evidence one variant outperforms the other. Safe to act on.
≥90% confidence Moderate evidence. Consider running longer to confirm.
Not significant No reliable difference detected yet.
Not enough data Fewer than 30 impressions on one variant. Too early to measure.

Rule of thumb: wait until you have at least 100–200 impressions per variant before drawing conclusions.

Click Export CSV to download all raw event rows (block_id, variant, event, timestamp).


Conversion triggers

Set in the Conversion Trigger section of the Inspector panel.

Trigger When it fires Extra field
Click on selector Visitor clicks a specific element inside the block CSS selector, e.g. .wp-block-button a
Form submit Any <form> inside the block is submitted —
URL visit The page loading counts as a conversion —
Custom JS event Your own code dispatches a named JS event Event name, e.g. gsab_purchase

Custom event example

Dispatch from anywhere in your JS:

document.dispatchEvent( new Event( 'gsab_purchase' ) );

Set the event name gsab_purchase in the block inspector under Custom JS event.


Inspector options reference

Option Default Description
Variant A traffic % 50 Percentage of visitors assigned to Variant A
Track impressions On Record a view event on every page load
Allow ?ab= override On Enable QA bypass via query parameter
Conversion trigger click What counts as a conversion
CSS selector — Element to watch for click trigger
Custom event name — JS event name for custom_event trigger
Show fixed variant to logged-in users Off Bypass split for logged-in users
Variant for logged-in users A Which variant logged-in users always see

Compatibility notes

Scenario Status
FSE / Site Editor templates ✅ Fully compatible
Classic themes ✅ Fully compatible
WooCommerce ✅ No conflicts
Full-page caching (WP Rocket, LiteSpeed, etc.) ⚠️ Exclude A/B-tested pages from page cache, or the same cached variant will be served to everyone
Classic Editor plugin ❌ Block editor must be active
REST API disabled (some security plugins) ❌ Tracking calls will fail silently (content still renders correctly)
GDPR cookie consent blocking gs_ab_* ⚠️ Falls back to IP hash — still deterministic but not sticky across days
WordPress Multisite ✅ Works per-subsite (table is blog-specific via $wpdb->prefix)
SQLite (WP 6.3 experimental) ✅ Uses dbDelta() which is SQLite-compatible

Developer hooks

greenshift_ab_assignment

Filters the variant assigned to a visitor before rendering.

add_filter( 'greenshift_ab_assignment', function( $variant, $block_id, $attrs ) {
    // Always show Variant A to logged-in users.
    return is_user_logged_in() ? 0 : $variant;
}, 10, 3 );
add_filter( 'greenshift_ab_assignment', function( $variant, $block_id, $attrs ) {
    // Show Variant B to visitors from a specific country (requires geo plugin).
    if ( my_geo_plugin_get_country() === 'DE' ) {
        return 1;
    }
    return $variant;
}, 10, 3 );

greenshift_ab_conversion_triggers

Add custom trigger types to the inspector dropdown.

add_filter( 'greenshift_ab_conversion_triggers', function( $triggers ) {
    $triggers['video_play'] = __( 'Video Play', 'my-plugin' );
    return $triggers;
} );

greenshift_ab_track_event

Filter or cancel a tracking event before it is stored. Return false to cancel.

add_filter( 'greenshift_ab_track_event', function( $data, $request ) {
    // Don't track known bots.
    if ( my_is_bot() ) {
        return false;
    }
    return $data;
}, 10, 2 );

greenshift_ab_storage_adapter

Replace the default DB storage with any custom backend. Return true after your own storage to skip the default table insert.

add_filter( 'greenshift_ab_storage_adapter', function( $handled, $block_id, $variant, $event ) {
    my_analytics_sdk()->push( $block_id, $variant, $event );
    return true; // skip wp_gsab_events table insert
}, 10, 4 );

REST endpoint

Tracking calls are made automatically by the frontend script. This documents the endpoint for custom integrations.

POST /wp-json/greenshift/v1/ab-track
Content-Type: application/json
X-WP-Nonce: <wp_rest nonce>

{
  "block_id": "abc123",
  "variant": 0,
  "event": "conversion"
}

Parameters:

Field Type Values
block_id string The block's unique ID (auto-generated)
variant integer 0 = Variant A, 1 = Variant B
event string "impression" or "conversion"

Response:

{ "recorded": true }

File structure

greenshift-ab-testing/
├── greenshift-ab-testing.php     Main plugin entry point
├── includes/
│   ├── class-gsab-database.php   Custom DB table (install, record, aggregate, export)
│   ├── class-gsab-tracking.php   REST tracking route
│   ├── class-gsab-admin.php      Admin dashboard + CSV export + significance
│   └── class-gsab-hooks.php      Developer hook documentation
├── blocks/
│   ├── ab-variant/
│   │   ├── block.json            Parent block definition
│   │   └── render.php            Server-side render (assignment + output)
│   ├── ab-variant-a/
│   │   ├── block.json
│   │   └── render.php            Transparent pass-through
│   └── ab-variant-b/
│       ├── block.json
│       └── render.php            Transparent pass-through
├── src/
│   ├── ab-variant/               Parent block editor JS + SCSS
│   ├── ab-variant-a/             Variant A editor JS
│   ├── ab-variant-b/             Variant B editor JS
│   ├── tracking/                 Frontend tracker (window.gsabTrack)
│   └── admin/                    Admin dashboard JS
├── build/                        Compiled JS/CSS (generated by npm run build)
├── assets/css/admin.css          Admin dashboard styles
├── webpack.config.js
└── package.json

License

GPL-2.0-or-later