وەسف
OW Consent is a consent management suite for WordPress. Eleven compliance profiles ship with it, from the GDPR and the UK PECR to the CCPA, the LGPD, PIPL and Quebec Law 25; the one in force decides the consent model, the Consent Mode defaults and the documents generated.
This version requires WordPress 6.2 or later. Sites on 6.0 or 6.1 are not offered the update.
Works behind a full-page cache
Nothing on the front end depends on the visitor’s consent cookie: the HTML is identical for every visitor, and the consent state is rebuilt in the browser from the cookie and applied before first paint. Trackers are blocked in the markup for everyone and released client-side for the categories the visitor granted, so a page cache (LiteSpeed, WP Rocket, Varnish, a CDN) cannot serve one visitor’s choices to another.
What it includes
- Cookie banner: four positions, light, dark or automatic theme, “Reject all” as prominent as “Accept all”, keyboard operable, translatable.
- Tracker blocker: scripts, inline snippets, iframes, pixels, resource hints, analytics stylesheets and media, from 183 editable signatures.
- Consent ledger: every action appended to a hash-chained table signed with HMAC-SHA-256, with the profile and the banner actually shown.
- Data-subject requests: shortcode form, e-mail verification, the Article 12.3 deadline, WordPress’s own exporter and eraser hooks.
- Legal documents: a privacy notice and a cookie policy per profile, in that profile’s language, fed by a site scanner that sweeps your own pages on WP-Cron, off by default.
- Google Consent Mode v2, IAB TCF v2.2, Global Privacy Control, geo routing, OW Forms integration: each off unless you enable it.
Limits, stated plainly
The generated documents are drafting aids, not legal advice: have them reviewed before publishing. The scanner reports what your HTML contains plus what an administrator-only probe observed; it is not a completeness guarantee. The TCF module is not a CMP registered with IAB Europe and offers no vendor-level choice, so vendors may reject its signal. The ledger is tamper-evident only while its signing key lives outside the database, and warns you when it is not.
Each module is documented in the FAQ below.
External services
OW Consent contacts one external service, and only when you enable the feature that requires it.
IAB Europe Global Vendor List
Used by the IAB TCF v2.2 module, which is disabled by default. The TCF framework requires the current list of registered ad-tech vendors; the plugin downloads it so it can build a TC String and expose the vendor set through __tcfapi.
When it happens: only when the “IAB TCF v2.2” option is enabled, from a daily WP-Cron job (or from the settings screen when you enable the option). It is never fetched while a visitor’s page is being rendered, and the public owco/v1/tcf/gvl REST route serves the cached copy only — it never triggers an outbound request.
What is sent: nothing. The request is a plain HTTP GET of a public JSON file at https://vendor-list.consensu.org/v3/vendor-list.json. No site data, no visitor data, no personal data and no identifier is transmitted; the only information the service receives is what your server necessarily discloses by making an HTTP request (its IP address and its user agent). The response is stored in a WordPress transient for one week and served from that cache; if a refresh fails, the previously cached list is kept.
Service provided by IAB Europe. Terms of use: https://iabeurope.eu/general-terms-use/ — Privacy policy: https://iabeurope.eu/privacy-policy/
Requests to your own site
The site scanner issues HTTP requests to your own site’s public URLs in order to inventory the trackers your pages load. These requests never leave your own host: the URL list is restricted to your site’s host after the owco_scanner_urls filter has been applied. No third party is involved.
MaxMind GeoLite2
If you place a GeoLite2 country database in wp-content/uploads/ow-geoip/ and enable the option, the plugin reads that local file with its own bundled reader. It never contacts MaxMind, and it never sends an IP address anywhere: the lookup happens on your server and the address is discarded immediately.
Tracker host names shipped as blocking signatures
The plugin ships a list of third-party tracker host names — Google Tag Manager, Meta, and so on — in data/tracker-catalog.json. They are blocking signatures. A host matching one of them is a host the plugin prevents the page from requesting until the visitor has consented to the matching category; the plugin makes no request to any of them, at any point, from the server or from the browser. The same list is what the browser-side guard compares an injected script against before refusing it, and what the generated cookie policy names as vendors.
The only host the plugin itself ever contacts is the Global Vendor List documented above, and only when the IAB TCF v2.2 module is enabled.
دامەزراندن
- Install the plugin from Plugins > Add New > Upload Plugin, or upload the
ow-consentfolder to/wp-content/plugins/. - Activate it through the Plugins menu.
- Open the OW Consent entry in the WordPress admin sidebar. The whole plugin lives on that single screen, in tabs: Dashboard, Banner, Compliance, Legal identity, Categories, Policies, Scanner, Tracker catalogue, Audit ledger and DSAR requests.
- Set your jurisdiction and consent model on the Compliance tab, then fill in the Legal identity tab. Those fields are what the generated documents are built from, and generation is refused while a mandatory one is empty.
- Generate your documents from the Policies tab, and have them reviewed before you publish them.
- Optionally, add
[owco_dsar_form]to a page to open the data-subject request portal, and[owco_dnsmpi]where you want the “Do Not Sell or Share” control. - Recommended: define
OWCO_LEDGER_KEYinwp-config.php, so the consent ledger’s signing key lives outside the database. The Audit ledger screen shows a freshly generated line to paste. - If your site sits behind a CDN, a load balancer or any reverse proxy, declare it with
OWCO_TRUSTED_PROXYinwp-config.php(the pluralOWCO_TRUSTED_PROXIESis accepted as an alias), giving the proxy addresses or CIDR ranges. The rate limiting on the public REST endpoints keys on the client address; when every request arrives from the same proxy address and the proxy is not declared, the plugin cannot tell one visitor from another and falls back to a per-visitor split backed by a wider site-wide ceiling, which is a coarser limit.
پهد
-
Does the plugin work with a page cache?
-
Yes, and it is designed around that. The front-end HTML it produces is identical for every visitor; consent is read from the cookie in the browser and applied before the page is painted. Trackers are blocked in the cached markup and released client-side. Only genuinely per-visitor responses — the DSAR verification page and REST responses containing personal data — are marked uncacheable. No nonce is printed in cacheable HTML either; the banner fetches one from a no-store endpoint before each write.
-
For most sites it covers the same ground: banner, blocking, audit log, data-subject requests, geo routing and generated documents. Two things it does not do: it is not a CMP registered with IAB Europe (see the TCF question below), and it does not provide legal advice or reviewed legal documents — the generator produces drafts that you must have reviewed.
-
Is the ledger tamper-proof?
-
It is tamper-evident, under one condition. Each row is signed with HMAC-SHA-256 over the previous row’s hash, so a modified or deleted row breaks the chain and verification reports where. The guarantee rests on the signing key being outside the database, which holds when you define
OWCO_LEDGER_KEYinwp-config.php. Without it, the plugin signs with a random key it keeps in the database: the chain is still built and checked, but an attacker with database access could re-sign it, and the admin tells you so. The key is never derived from WordPress’s authentication keys and salts. -
Does the scanner slow down my site?
-
No. It is off by default, and once enabled it runs as a WP-Cron job at the frequency you choose (weekly by default), fetching a sample of your own pages server-side. It never runs during a visitor’s page view. Note that WP-Cron only fires when your site receives traffic, or when you have set up a real cron task; on a site with
DISABLE_WP_CRONand no system cron, the scheduled scan will not run. -
Partly. The scheduled server-side sweep reads the
Set-Cookieheaders your pages return, so it finds cookies set by the server, including HttpOnly ones, as well as scripts, iframes, stylesheets and pixels. It executes no JavaScript, so cookies written by scripts in the browser are not visible to it; those are collected by a diagnostic probe that by default runs only for logged-in administrators. Treat the result as an inventory of what was seen, not as an exhaustive list — and both screens say so rather than leaving you to assume otherwise: the Scanner tab states whether the last sweep finished, how many pages it read, how many it could not fetch and whether a browser has ever reported back, and the generated cookie policy repeats the same reservations to the reader. -
How does a data-subject request get fulfilled?
-
The visitor submits the form and receives a verification e-mail. Confirming the request on the linked page starts the Article 12.3 deadline and opens a WordPress personal-data request. From the DSAR screen you can download a JSON export produced by every exporter registered on the site (OW Consent’s own records, WordPress core’s, and any other plugin’s), run every registered eraser, and close the request with a written response that is stored with it. What the export and the erasure cover depends on which plugins on your site register those hooks; anything that registers none has to be handled by hand.
-
Does it integrate with OW Forms?
-
Yes, when OW Forms is active and the integration is enabled. Every stored submission is linked to the consent in force at that moment: the pseudonymous browser token, the ledger entry that was current, the categories granted, the compliance profile, the hash of the published documents, the timestamp, and the exact wording of the consent checkbox that was accepted. Erasure requests cascade to OW Forms submissions and their uploaded files, matched on the e-mail hash OW Forms itself stores, and OW Forms submissions are included in WordPress’s Export and Erase tools.
Three limits are worth stating: no consent event is written into the ledger for a submission (the link references the entry already in the chain); submissions recorded before 1.2.0, and submissions made while OW Forms is configured not to store them, have no link; and the e-mail address remains stored in clear text by OW Forms, because it is needed to answer the sender — the hash is a lookup key, not a privacy measure.
-
Can I use the TCF module for Google AdSense or Ad Manager?
-
Only with your own CMP ID issued by IAB Europe, and even then with a caveat. Without a CMP ID the module does not load at all. With one, it emits a correctly encoded TC String and the panel displays every purpose and special feature that string can claim, but TCF policy requires a registered CMP to present purpose-level and vendor-level choices of its own; here the purpose switches follow the categories and there is no vendor-level choice, so it is not a registered CMP and vendors are entitled to reject its signal. If TCF-gated advertising revenue matters to your site, use a certified CMP.
-
What language are the generated documents in?
-
The privacy notice and the cookie policy are generated for all eleven profiles in the jurisdiction’s own language: English, French for France, Belgium, Luxembourg and Quebec, Portuguese for Brazil. The legal notice and the terms exist only in French, built around French law; for any other jurisdiction the generator refuses to produce them rather than publishing an inappropriate document.
-
Is the interface itself translated?
-
The interface is written in English and is entirely translatable: every string goes through gettext under the
ow-consenttext domain, andlanguages/ow-consent.potships as the template. No translation is bundled with the plugin. Locale packs are built on translate.wordpress.org and installed and loaded by WordPress itself, which is how a plugin hosted here is meant to be translated; to have the interface in your language, contribute or review the strings for that locale there. -
Four positions (bottom bar, bottom card, centre modal, top bar), a light, dark or automatic theme, an optional site logo (your custom logo, falling back to the site icon), configurable labels and a floating re-open button. Any link or button carrying the class
owco-open-preferencesreopens the preferences panel.“Reject all” is rendered with the same prominence as “Accept all”. The close cross is disabled by default; when you enable it, closing the banner is recorded as a refusal of every optional category, never as a silent dismissal. It is present in the markup either way, hidden while the setting is off, and it is revealed and relabelled when a refusal cannot be recorded at all: a visitor whose choice the server will not accept is not left facing a banner they cannot dismiss, and that dismissal is not counted as a choice. The panel is keyboard operable, uses
role="switch"toggles with a visible on/off state, traps focus in modal mode and restores it on close. Banner copy is translatable and can be replaced through theowco_banner_textsfilter. -
What exactly does the tracker blocker block?
-
Third-party scripts, inline tracking snippets, iframes, tracking pixels (including inside
noscript), analytics and marketing stylesheets and media sources are rewritten before consent and released afterwards, in document order. Resource hints (preconnect,dns-prefetch,preload) aimed at a catalogued third party are dropped outright rather than deferred, since a hint opens a connection and has nothing to restore. Stylesheets and web fonts served by a host classified as functional are left alone: neutralising them costs every first-time visitor a fallback-font page and buys no consent. Lazy-loading attributes used by caching plugins are neutralised so a lazy loader cannot restore a blocked URL. Blocked embeds are replaced by a placeholder carrying a per-category unblock button.The plugin ships 183 detection signatures in
data/tracker-catalog.json(118 URL patterns and 65 cookie names). The catalogue is editable from the Tracker catalogue screen — add, edit, delete, search, plus a view listing the rules that cannot match — and extensible through theowco_scripts_catalogfilter. Unknown third-party scripts are allowed by default and unknown third-party iframes are blocked by default; both are settings, and a host allowlist (also exposed as theowco_blocker_allowlistfilter) always wins. -
What is recorded in the consent ledger?
-
Every consent action (accept, reject, partial save, GPC opt-out, renewal, withdrawal) is appended to a hash-chained table. Each row’s HMAC-SHA-256 covers the previous row’s hash and is written inside a transaction, so concurrent writes cannot fork the chain. Rows store a pseudonymised IP (IPv4 last octet zeroed, IPv6 truncated to /48), a user-agent hash, the page URL, the compliance profile, a hash of the legal documents in force, a fingerprint of the banner actually displayed and the plugin version — the elements needed to reconstruct what the visitor was shown, for GDPR Article 7(1) accountability.
Chain verification walks the whole chain, reports the number of rows actually checked and describes each break individually, including a key rotation, which it distinguishes from a rewrite. Rows written before 1.2.0 carry an unkeyed digest that anyone with database access can recompute; verification counts them separately and refuses to declare a chain intact while any of them remain, because an unkeyed digest is not evidence.
-
What safeguards are on the data-subject request form?
-
The shortcode form (
[owco_dsar_form]) collects access, rectification, erasure, portability, restriction, objection and CCPA opt-out requests. Submitting it requires an explicit declaration that the right is being exercised over the requester’s own data, checked server-side and recorded with the request. The requester receives a verification e-mail; the link opens a confirmation page, and the request is only confirmed by an explicit action on that page, so a mail security scanner following the link cannot confirm an identity on the subject’s behalf. The token is single-use, stored only as a SHA-256 hash, and expires after a configurable number of days (7 by default).Verification starts the Article 12.3 clock (30 days by default, configurable from 1 to 30) and opens a matching WordPress core request. Reminders are sent seven days before the deadline and once it has passed, and the one-off two-month extension permitted by Article 12.3 notifies the data subject of the reasons. A request whose author never confirmed their identity cannot be exported, erased or recorded as honoured: the check is enforced on the server, not only by hiding the buttons, so nobody can assemble or destroy the record held about an address on the strength of a claim that was never proved.
-
What does the legal document generator produce?
-
A privacy notice and a cookie policy for each of the eleven compliance profiles, written against that jurisdiction’s own statute and in that jurisdiction’s language. The legal notice and the terms exist as French documents only; generation for a non-francophone profile is refused with an explicit error rather than publishing a French document to an English-speaking audience.
Documents are built from the settings on the Legal identity tab, and generation is blocked with the list of the fields still empty rather than publishing blank clauses. The cookie policy lists what the scanner actually found, with the vendor and a link to that vendor’s own privacy policy. Unless you turn it off, every document ends with a notice stating that it is an automatically generated template that must be reviewed by a qualified professional before publication, with the generation date and the plugin version. Bodies and variables can be replaced through the
owco_policy_template,owco_policy_varsandowco_policy_htmlfilters.The tracker table states what the sweep behind it actually covered, in the document’s own language: whether a sweep has ever run, whether the last one could read no page at all, whether it was interrupted and after how many pages, how many pages could not be fetched, whether any browser has ever reported what the pages load at runtime, and the window beyond which a tracker no longer seen is dropped from the list. A finding the blocker cannot suppress — a cookie set by a
Set-Cookieresponse header — is marked as such and the clause claiming that unclassified trackers are set only after consent carries the matching exception. An incomplete sweep is disclosed rather than published as a finished inventory. -
How do I know what the scanner’s findings actually cover?
-
Once enabled, the scanner runs on the WP-Cron schedule you choose — hourly, twice daily, daily or weekly (weekly by default). A sweep fetches a sample of your own public pages server-side (25 by default) and records third-party scripts, inline tracking signatures, iframes, stylesheets, tracking pixels and the cookies named in
Set-Cookieresponse headers. Each finding records where it came from and whether the blocker can actually act on it, and can be classified into the blocking catalogue in one click.The Scanner screen carries a coverage panel stating what the current findings rest on: whether a sweep has ever run, whether the last one finished or was interrupted by its time budget, how many pages it actually read, how many it could not fetch and the first error returned, and whether a browser has ever reported back through the probe. It is built from the stored status of the last sweep, so it is as accurate after a scheduled run as after a manual one, and a run that reached no page is shown as a failure rather than as a clean result. That is the panel to read before publishing a cookie policy from these findings.
-
Which Google Consent Mode v2 signals are emitted?
-
All seven:
ad_storage,ad_user_data,ad_personalization,analytics_storage,functionality_storage,personalization_storageandsecurity_storage. The six consent categories (necessary, functional, analytics, marketing, preferences, social) are mapped to them from a single source of truth, so the banner, the blocker and the page bootstrap cannot disagree. The default call is printed as static markup and the update is computed in the browser, which keeps the page cacheable. -
What does the IAB TCF v2.2 module do?
-
It exposes
__tcfapi, the__tcfapiLocatoriframe and a TC String encoder, and writes theeuconsent-v2cookie only after the visitor has acted — never on a first view. It requires a CMP ID assigned to you by IAB Europe: with no CMP ID the module refuses to load at all, rather than emitting a string carrying CMP ID 0, and it refuses to invent a vendor-list version when the Global Vendor List has not been cached yet. The vendor list is stored in a transient, refreshed by a daily cron job, kept for a week, and served stale rather than re-fetched when a refresh fails.When the module is enabled, the preferences panel shows one switch per TCF purpose the string can actually claim on this site and one checkbox per special feature the publisher has declared, so nothing is encoded into the string that was not displayed. A special feature stays off unless it is ticked; “Accept all” does not turn it on.
-
How are the CCPA opt-out and Global Privacy Control handled?
-
A “Do Not Sell or Share My Personal Information” control is injected in the footer, or placed with the
[owco_dnsmpi]shortcode. The first click records the opt-out directly, as California Regs 7026(a)(1) requires, rather than merely opening the preferences panel. On a site configured under a US profile the control is visible for everyone, with no JavaScript involved. When geo routing is enabled, the same markup is served to every visitor and revealed in the browser for those whose own jurisdiction requires it, so a mandatory link is never decided by whichever visitor happened to populate the page cache. Sites with geo routing off render nothing extra.The Global Privacy Control signal (the
Sec-GPCheader andnavigator.globalPrivacyControl) is treated as a binding universal opt-out under the CCPA and US state profiles: the opt-out is recorded once per browser session, and the visitor is told when it replaced a choice they had previously saved. Under GDPR-family profiles GPC is treated as a hint — optional categories are pre-denied in the interface and the banner is still shown — because consent there must be an affirmative act. -
How does geo-based profile routing work?
-
It is optional and off by default. When enabled, the visitor’s country is resolved from Cloudflare
CF-IPCountry, CloudFrontCloudFront-Viewer-Country, a server-setGEOIP_COUNTRY_CODE, or a local MaxMind GeoLite2 country database read by a reader bundled with the plugin (no Composer package and no vendor directory required). Proxy headers are trusted only when the site declares the proxy withOWCO_BEHIND_CLOUDFLARE,OWCO_BEHIND_CLOUDFRONTorOWCO_GEO_TRUSTED_HEADERS; without a trusted source and without a database, detection deliberately fails closed to the strictest opt-in profile. The IP address is used for the lookup and immediately discarded — never stored, never logged.Geo routing does not make the HTML vary. The page carries both consent models — the opt-in defaults and the opt-out defaults — and the browser selects the one matching the visitor’s jurisdiction before the first Consent Mode default call is emitted. The country itself is resolved on a request that reaches PHP and cached in
owco_geofor 24 hours, so a visitor whose very first page is served by the cache is treated under the site’s configured profile until that cookie exists. -
Which compliance profiles are available?
-
Eleven: GDPR, UK PECR, Swiss nFADP, LGPD, POPIA, PIPL, DPDP, Quebec Law 25, CCPA, Australian Privacy Act and a generic US state profile. The profile drives the consent model (opt-in, or opt-out for CCPA, the generic US profile and Australia), the Consent Mode defaults, whether the “Do Not Sell or Share” control is shown, whether GPC is binding, which privacy notice and cookie policy are generated, which rights are listed and which supervisory authority is named.
-
All of them are first-party, none of them is used for tracking or analytics, and they are declared in the generated cookie policy.
owco_consent— the visitor’s choices, their pseudonymous browser token and the profile in force. Lifetime follows the renewal setting (12 months by default, capped at 13). A renewal setting of 0 is treated as that 13-month maximum, not as “never expires”, and the same figure governs the cookie, the server-side expiry check and the duration the generated documents publish.owco_geo— the detected country and the resulting profile, for 24 hours. Written only when geo routing is enabled and a country was actually detected. It contains no IP address.owco_gpc— a session marker recording that the Global Privacy Control signal has already been processed for this browser, so the signal is not re-recorded on every page view.owco_gpc_notice— five minutes, so the interface can tell the visitor that GPC replaced the choice they had saved.euconsent-v2— the IAB TCF consent string, written only when the TCF module is enabled and only after the visitor has acted.
-
What happens to my data if I delete the plugin?
-
The consent ledger, the scanner findings, the tracker catalogue, the OW Forms consent links, the options and the settings survive deactivation and deletion by default. Two things are removed whatever the “Delete all data on uninstall” setting says.
The scheduled jobs are always unscheduled, so deleting the plugin cannot leave a cron event pointing at code that is gone. And the data-subject request register — the names, e-mail addresses and free-text messages people sent you, plus its rate-limiting transients — is always dropped. It is the only table holding directly identifying personal data about other people; once the plugin is gone nothing enforces the retention period that bounded it, and no screen is left that could read, answer, export or erase it. Export the requests you need to keep from the DSAR screen before you delete the plugin.
For a clean removal of everything else, enable “Delete all data on uninstall” before deleting the plugin — it then drops the plugin’s tables, options and transients, on every site of a multisite network.
پێداچوونەوەکان
هیچ پێداچوونەوەیەک نەنووسراوە بۆ ئەم پێوەکراوە.
بەشداربووان و گەشەپێدەران
“OW Consent” نەرمەواڵەیەکی سەرچاوە کراوەیە. ئەم کەسانەی خوارەوە بەشدارییان تێدا کردووە.
بەشداربووان“OW Consent” وەربگێڕە بۆ زمانەکەی خۆت.
دەتەوێت بەشداربیت لە گەشەپێدان؟
گەڕان لە کۆدەکەدا بکە، سەیری تەمارگەی (SVN) بکە، یان بەشداربە لە ڕووداوتۆماری گەشەپێدان لە ڕێگەی (RSS).
ڕووداوتۆمارگەریی گۆڕین
2.0.8
The browser now rejects a consent cookie signed with a key the server no longer accepts, as the server already did. Cookies written before the 2.0.5 key change were still honoured in the browser; those visitors are now asked again.
2.0.7
The policy links appended to the footer menu are passed through wp_kses_post() on return.
2.0.6
Plugin URI and Author URI point straight at the plugin’s page and OptionWeb’s English home, without a redirect. The OptionWeb entries of the tracker catalogue linked to missing pages; they now link to OptionWeb’s privacy policy, installed catalogues included.
2.0.5
The consent ledger, the consent cookie and every other keyed hash no longer use the WordPress authentication salts. The ledger key is OWCO_LEDGER_KEY when defined, otherwise a random key the plugin keeps; rows signed before the update are attested under it, so later edits are still detected. Visitors see the banner once more. The DSAR and CCPA shortcodes escape their output on return.
2.0.4
JSON exports keep wp_json_encode()‘s default escaping.
2.0.3
Styles and scripts ship as files under assets/; only JSON configuration stays inline. The scripts that must run first are excluded from optimiser deferral.
2.0.2
Two phpcs:ignore annotations corrected.
2.0.1
- The autoloader still used the old prefix’s length, so the lazily-loaded classes resolved to files that do not exist: policy pages, DSAR submissions and every
/owco/v1/*route failed with a fatal error since 2.0.0. Treat consent writes from that window as unrecorded. - The 2.0.0 migration missed the OW Forms consent-link table, so OW Forms submissions stopped recording their consent link. It is now migrated.
2.0.0
Renames everything the plugin owns from owc to owco (the directory requires a prefix of at least four characters). Existing installations are migrated automatically. Custom code hooking owc_* filters or CSS targeting owc-* classes must be updated.
- Security:
GET /owco/v1/nonceno longer mints awp_restnonce, which authorises writes across the whole REST API and could be harvested cross-origin. It now mints a token scoped to this plugin’s own write routes and refuses a foreign-origin request. - The CCPA footer control is now printed through
wp_kses(). The admin stylesheet has its own handle instead of core’swp-admin.
1.5.0
Scripts and stylesheets go through the WordPress asset API instead of being echoed. Translations are no longer bundled. The unused GET /owc/v1/state route is removed. Admin notices are confined to the plugin’s own screens.
1.4.3
Fixed the consent and geo cookies being percent-encoded twice, which made every browser discard the choice while server-side checks kept passing. Verified end to end on PHP 7.4 and 8.3.
1.4.2
Fixed the 1.4.1 cookie reader failing on a valid cookie — same double-encoding cause as 1.4.3, caught here for the read path. Update if you installed 1.4.1.
1.4.1
Fixed consent writes being lost on a site whose visitors browse a different host than the Site Address setting (www vs. apex, an alias, a proxy). The banner now re-anchors its REST call on the origin actually browsed.
1.4.0
Public write endpoints no longer depend on WordPress’s core nonce, which a full-page cache serves stale or foreign. Refused writes are recorded and reported on a new Dashboard panel.
1.3.2
Fixed three misplaced phpcs:ignore annotations from 1.3.1. Translation catalogues regenerated.
1.3.1
Packaging-only: rewrote three queries for static-analysis compliance, no behavioural change.
1.3.0
WordPress 6.2 is now the minimum, for the %i identifier placeholder. A write this site cannot actually record is now refused with a 503 instead of reported as saved, and the banner no longer treats a cache/firewall interstitial as a successful write. The www/apex spellings of the site’s own host are both accepted by the same-origin check. Thirteen Plugin Check errors closed.
1.2.0
The architectural rewrite: cache-independent front end, a hash-chained HMAC-signed consent ledger, genuine DSAR fulfilment via WordPress’s personal-data exporter/eraser hooks, per-jurisdiction privacy notices and cookie policies (11 profiles), rate limiting on every public REST route, IAB TCF v2.2 and Google Consent Mode v2 support, MaxMind GeoLite2 geo-routing, and a French translation. 349 defects closed against the 1.1.0 baseline.
1.1.0
Initial public release.
