Version 5.3.0
October 15, 2026
Added
{% experiment %}takes any expression as its handle, so a page-builder block can pass its own field:{% experiment block.experimentHandle %}. Variant handles are still string literals. (#7)winkRender(handle, bodies)renders an experiment block from variant bodies the template has already rendered, keyed by variant handle — cache-safe or server-side as the experiment is delivered, like the tag.DeliveryService::render()andrenderServerSide()do the same from PHP. (#7)
Fixed
- An
{% experiment %}block inside another's variant overwrote the outer block's state, so the outer block rendered as the inner experiment.
Version 5.2.0
October 15, 2026
Added
- A Require Consent setting (
requireConsent). With it on, Wink sets no cookie and records nothing for a visitor until they consent withWink.consent()(or awink:consentevent, or the_wink_consentcookie); until then they see each experiment's control.Wink.revokeConsent()withdraws it. (#6) - The experiment edit screen can add and remove variants and goals, shows the running weight total, and chooses the control and the primary goal with a radio. (#5)
Changed
- Saving an experiment checks its variants and goals first: at least two variants, weights adding up to 100, one control, at least one goal, unique handles, and a target for page view, click and form submit goals. Nothing is saved unless everything is valid, and the form comes back with the errors.
Wink.convert()sends the goal alone, and the server counts it for every running experiment with that goal the visitor has seen, so it works on pages without the experiment.- Click goals are detected on elements added after the page loaded, and GA4/GTM events are forwarded as they happen rather than when the batch is sent.
- Chart.js 4.5.1 is bundled with the plugin instead of loaded from a CDN.
Fixed
- Start, Pause, Complete and Archive failed with a 400, so an experiment couldn't be started from the control panel. (#1)
- Page View and Form Submit goals never recorded a conversion; only Click goals were detected. (#2)
- Conversions sent from a page without the experiment block were dropped. (#3)
- The report's "Conversion Rate Over Time" chart never loaded. (#4)
- The experiment edit screen had no way to add a third variant or a second goal. (#5)
- The control panel script and styles weren't loaded, so the handle wasn't generated from the title.
- Re-adding a removed variant or goal handle in one save failed on the unique index.
Version 5.1.0
October 8, 2026
Wink now has cache-safe delivery, and turns it on for you behind Blitz. The new Delivery setting defaults to Automatic: cache-safe when Blitz is caching, server-side otherwise. If a CDN or any other full-page cache serves your HTML, Wink can't see it — set Delivery to Cache-safe, or your experiments show every visitor the first visitor's variant.
Added
- Cache-safe delivery. Every variant of an
{% experiment %}block (orwinkVariant()) is rendered into markup that is the same for every visitor, hidden by CSS, and a small inline script assigns the visitor in the browser — with the server's hash, enrollment roll, weights and cookie, so server- and browser-assigned visitors agree — and reveals their variant before it paints. Without JavaScript, the control shows. The impression is recorded through/wink/track, by the tracker or, ifwinkTrackingScript()isn't on the page, by the inline script. - A Delivery setting (
deliveryMode:auto,serverorcacheSafe), and a per-experiment override on the experiment edit screen. - Blitz detection. The settings page and the experiment editor warn when server-side delivery is
chosen while Blitz is caching.
DeliveryService::EVENT_DETECT_PAGE_CACHElets a site report any other full-page cache.
Changed
- The visitor cookie is now raw and readable by script, so the browser can assign the visitor the server issued. It still holds only a random ID. A signed cookie from 5.0 is honoured and rewritten, and a cookie value Wink wouldn't have minted is replaced.
- An event sent from a cache-safe block carries the visitor ID the browser assigned with; the tracking endpoint skips it if the cookie names someone else, rather than count a variant that wasn't shown.
Fixed
- The README and site promised cache-safe delivery, but variants were always chosen during the render, so behind Blitz or a CDN every visitor got the cached variant while impressions were counted against each visitor's own assignment.
- The experiment edit screen failed with a Twig error (
Variable "forms" does not exist). - Events queued when the visitor left the page were sent with
sendBeaconastext/plain, which Craft doesn't parse, so they were lost. They are now sent as JSON with a keepalive request.
Version 5.0.6
October 4, 2026
Wink now has permissions. Until now every control panel user could manage experiments, read reports and change settings. After updating, grant Manage experiments and View experiment reports under Settings → Users to everyone who needs them. Settings are now admin-only. Behind a proxy or CDN, set Craft's
trustedHosts, so the new per-address tracking budget charges visitors rather than the proxy.
Security
- Wink registered no permissions, so any control panel user could create, edit, start, end and delete experiments, declare winners, read reports and change settings. There are now two permissions: Manage experiments and View experiment reports. Variant content is published on the site as HTML, so Manage experiments is effectively the right to edit templates. The experiment element checks them too, so the element index's Delete action follows them.
- Settings are admin-only, and saved only where admin changes are allowed. The cookie name must now be a valid cookie token.
- The public tracking endpoint took any number of events of any kind. It now takes at most 25 a
request, of type
impressionorconversion. There is a per-address budget,trackingBudgetPerMinute(120 by default), that a forgedX-Forwarded-Forcan't reset. - A conversion was recorded for a goal the experiment doesn't have, and from visitors who had never seen the experiment. It now needs both: a known goal, and an impression first.
- Click-goal selectors and the GTM event name are written into an inline
<script>. They are now hex-escaped, so a</script>in one can't close it.
Fixed
- Conversion rates counted conversion events, so a returning buyer counted twice and a variant's rate could pass 100%, breaking the confidence interval and the significance test. Rates, intervals, the z-test and the chart now count converted visitors. Every conversion is still recorded.
- With Anonymize IP on, IPv6 addresses were stored whole. They now keep only their first 48 bits.
- Client-supplied URLs, referrers and user agents longer than their 500-character columns failed the insert. They are now truncated. Event metadata is limited to 2 KB of JSON.
Version 5.0.5
July 26, 2026
Fixed
- Element index columns (status, traffic %, variant count) rendered blank or raw. The overridden method was renamed from
tableAttributeHtml()toattributeHtml()for Craft 5, which had silently disabled it. - Winner determination could declare a significantly worse variant the winner. The two-proportion z-test is two-tailed, so significance alone doesn't imply improvement — a variant must now actually beat the control to win.
- Visitor IDs were unstable within a single request. Each call to
getVisitorId()re-read the request cookie and minted a fresh UUID, scattering one visitor's assignments and impressions across throwaway IDs whenever a page contained more than one experiment. The ID is now memoized per request. - Conversion goals are returned in a stable, author-defined order instead of alphabetically. This also fixes the wrong goal being deleted when goals were re-saved.
Added
- Codeception integration test suite running against Craft's test framework (element CRUD, queries, services, tracking, report generation, Twig rendering), plus expanded PHPUnit unit coverage (enums, the
{% experiment %}tag compiler, PSR-4 layout). Includes a DDEV environment andddev testrunner.
Changed
- Moved
VariantReportinto its own file (src/models/VariantReport.php) to comply with PSR-4; previously it was only loadable as a side effect of loadingExperimentReport.
Version 5.0.4
June 11, 2026
Fixed
- Fatal
ParseErrorinExperimentsControllerandSettingsControllercaused by an unescaped apostrophe in the "Couldn't save…" error messages, which prevented the plugin from loading.
Version 5.0.3
June 11, 2026
Fixed
- PHP 8.4 compatibility:
Experiment::defineSources()anddefineActions()now declare their parameters as explicitly nullable (?string), resolving an implicit-nullable deprecation that becomes a fatal error in PHP 9.0.
Added
- PHPUnit unit test suite covering the statistical engine (two-proportion z-test, Wilson score interval) and deterministic variant assignment.
Changed
- Plugin schema version aligned to 5.0.0.
Version 5.0.0
May 2, 2026
Added
- Initial release of Wink for Craft CMS 5.
- Experiments as a custom Craft element type with full index and CRUD.
- Variants with weighted traffic allocation.
- Conversion goals (pageview, click, form submit, custom event).
- Server-side deterministic variant assignment (no flicker).
- Frontend JavaScript tracker with event batching.
- Statistical significance via two-proportion z-test.
- Wilson score confidence intervals.
- Automatic winner determination.
- CP reports with Chart.js visualizations.
- Twig functions:
winkVariant(),winkExperiment(),winkTrackingScript(). {% experiment %}block tag for inline variant content.{{ craft.wink.* }}template variable.- Optional GA4/GTM event forwarding.
- Do Not Track respect.
- IP anonymization option.
- Event retention management.