WordPress
The Zero Ad Network plugin connects your WordPress site and applies the subscriber experience through integrations with ad, consent, popup, paywall, and membership plugins.
The plugin is published in the WordPress.org plugin directory.
Before you start
You need WordPress 4.9+, PHP 7.2+ with the Sodium extension, and your Publisher ID: zapub_7Fq2xR9nKd3wV8mB4tL6yH1c (example Publisher ID). You can also copy it from Sites & creators. Sites are discovered automatically when the extension records a visit; there is no separate site registration step.
The plugin verifies Freedom membership. Use the Freedom access box in the post editor to choose which paid posts and pages subscribers can read.
Install and configure
- In WordPress, open Plugins > Add New Plugin, search for Zero Ad Network, then install and activate it. Alternatively, download the ZIP from WordPress.org and install it using Upload Plugin.
- Open Zero Ad Network > Settings in the WordPress admin menu.
- Paste your Publisher ID and select Enable Plugin.
- Leave Publisher Header Method set to HTTP Response Header unless your hosting requires the HTML Meta Tag option.
- Save your settings. If you sell access, edit each included post or page, check Freedom access > Include this content with Freedom, and save it. Offer your base subscription content or a custom selection that unlocks paid content or functionality.
- Clear existing page caches, then verify included and excluded content as both a subscriber and a regular visitor.
The plugin announces Better-Web-Publisher and verifies incoming Better-Web-Token headers locally. It derives the allowed hostnames from your WordPress and Site Address URLs, so those must match the public domains you serve.
What changes for subscribers
On a normal frontend page, a verified subscriber gets all of the supported integrations at once:
- Ads and known non-essential tracking scripts are suppressed.
- Cookie consent banners and marketing popups are removed or hidden.
- Selected published posts and pages gain reading access through the supported membership hooks.
- Unselected content, higher tiers, private posts, drafts, and password-protected posts keep their restrictions. The plugin does not grant purchases, site membership records, or administrative capabilities.
These changes apply to verified subscribers. Missing or invalid tokens keep the regular experience. The page transformations do not run on WordPress admin, AJAX, REST API, or JSON responses.
The plugin cannot cover every theme or custom ad placement. Use the compatibility reference below, then check actual pages, including protected posts and scripts loaded by your theme.
Page caches and CDNs
A page cache can serve HTML before WordPress runs, preventing the plugin from checking the token. Configure every caching layer to bypass cache reads and writes when the request includes Better-Web-Token, and forward that header to WordPress. This lets the plugin verify the visitor and keeps the clean response out of the shared cache.
The plugin marks token-bearing responses Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0, sends CDN/LiteSpeed no-store signals, and defines DONOTCACHEPAGE. Public responses remain cacheable and vary on Better-Web-Token to prevent a browser reusing the ordinary page after a token appears. It does not create subscriber variant cookies or cache entries. A supplied zeroad_variant cookie or X-ZeroAd-Variant header never grants access.
WordPress and server caches
PHP page caches can load through advanced-cache.php before regular plugins. In an installation already using WP_CACHE, replace that definition in wp-config.php, before WordPress loads:
$freedomTokenRequest = array_key_exists('HTTP_BETTER_WEB_TOKEN', $_SERVER);
define('WP_CACHE', !$freedomTokenRequest);
if ($freedomTokenRequest && !defined('DONOTCACHEPAGE')) {
define('DONOTCACHEPAGE', true);
}
Do not add a second definition. This bypasses the PHP drop-in, not an upstream CDN or a web-server rewrite serving cached HTML. WordPress loads the drop-in early.
| Cache | Required configuration |
|---|---|
| WP Super Cache | Use PHP/Simple delivery with the early bypass. Expert/rewrite delivery also needs a server exclusion before cached-file delivery. |
| WP Rocket, W3 Total Cache, WP Fastest Cache | Apply the early bypass wherever the PHP drop-in is used. Configure any direct cached-file delivery separately. Automatic compatibility is not claimed. |
| LiteSpeed Cache | Exclude token requests before server cache lookup. The plugin also marks origin responses non-cacheable using LiteSpeed's hook/header. |
| Apache, CloudFront, managed hosting | Configure both lookup and storage bypass and forward the token header. Disable HTML caching on participating pages if these controls are unavailable. |
WP Super Cache's Simple and Expert modes run at different layers. LiteSpeed documents its no-cache response hook. Neither makes a regular WordPress plugin run before an upstream cache hit.
Nginx
Add a map in http, then the matching directives to your existing caching location. Preserve other cache exclusions:
map $http_better_web_token $freedom_skip_cache {
"" 0;
default 1;
}
# proxy_cache location:
proxy_cache_bypass $freedom_skip_cache;
proxy_no_cache $freedom_skip_cache;
proxy_set_header Better-Web-Token $http_better_web_token;
# OR fastcgi_cache location:
fastcgi_cache_bypass $freedom_skip_cache;
fastcgi_no_cache $freedom_skip_cache;
fastcgi_param HTTP_BETTER_WEB_TOKEN $http_better_web_token;
The map ensures even an invalid token of 0 bypasses. Nginx treats an empty header like an absent one here; neither grants access. Both cache bypass and no-cache storage controls are necessary. Do not configure the cache to ignore private/no-store responses.
Varnish and Cloudflare
In Varnish, put this before cache lookup or early returns in vcl_recv, preserving the header to the backend:
if (req.http.Better-Web-Token) {
return (pass);
}
For Cloudflare, use a Cache Rule with Cache eligibility: Bypass cache and this expression:
http.request.headers.truncated or has_key(http.request.headers, "better-web-token")
The header map uses lowercase names, so this handles mixed-case HTTP/1 headers too. Truncated header inspection also bypasses. Ensure other rules or Workers do not override the bypass or strip the token.
Acceptance checks
Clear all page caches after configuration or included-content changes. Check the same URL in this order:
- Without a token, warm the ordinary public cache.
- With a valid token and no cookies, confirm WordPress verifies it and unlocks only included content on the first visit.
- Repeat the valid request: it must still bypass lookup and storage, with a private/no-store response.
- Remove the token: the ordinary protected page must remain intact.
- Repeat with invalid, expired, and wrong-host tokens, plus forged
zeroad_variant=subscriber1cookies andX-ZeroAd-Variantheaders. None may grant Freedom access. Existing WordPress membership permissions remain independent.
Inspect each cache layer's logs or status headers. A MISS alone is insufficient: it may still populate a cache. These are deployment checks; plugin unit tests do not certify third-party cache installations.
Expired tokens stop unlocking content, subject to the SDK's default 60-second clock tolerance. Cancellation or closure stops renewal but cannot revoke a signed offline token before expiry. Verification-result caching never extends a successful cached verdict beyond the token's expiry.
Token verification caching
Under Performance & Caching, Enable APCu Caching controls reuse of token verification results. With APCu available, results can survive between PHP requests; otherwise the plugin falls back to memory for the current request. Cache TTL (seconds) sets the verification cache duration.
This setting does not configure a CDN or full-page cache. Verification still works without APCu.
Check the integration
- Visit a public page and confirm it includes
Better-Web-Publisheras the configured response header or meta tag. - Once your site appears in the dashboard, open its page and use Test in your browser with the extension installed.
- Visit pages containing ads, cookie banners, and popups. Check that selected paid content opens, while unselected, private, draft, and password-protected content stays protected.
- Open the same URLs without the extension and confirm that the regular experience still works.
If the site is not detected, check that the plugin is enabled, the Publisher ID is correct, and old cached pages have been cleared. If subscribers still see ads or a paywall, first check that their requests reach WordPress with Better-Web-Token; then check the relevant plugin integration.
Supported plugins reference
The tables below describe the integrations targeted by the current plugin code. They use plugin hooks, script removal, and CSS depending on the integration. They are not a certification of every third-party plugin version: check your installed versions and theme with a subscriber visit.
Identifiers are the text domains the plugin checks for active plugins, unless a callback or filter is named. They are not necessarily WordPress.org download slugs. Custom markup and plugin updates can need additional integration.
Advertisements
| Plugin | Detection or hook |
|---|---|
| Ads For WP | ads-for-wp |
| Ad Inserter | ad-inserter |
| Advanced Ads | advanced-ads |
| WP Quads | quick-adsense-reloaded |
| AdRotate | adrotate |
| Advanced Ads - Google AdSense In-feed | advanced-ads-adsense-in-feed |
| AdWidget | AdWidget_ callbacks |
| WP AdCenter | wpadcenter |
| Corner Ad | corner-ad |
| Ad Injection | ad-injection |
| Simple Ads Manager | simple-ads-manager |
| BuddyPress Ads | buddypress-ads |
| Google Site Kit (AdSense) | googlesitekit_adsense_tag_blocked filter |
Raptive/AdThrive is not listed as confirmed support. Its existing suppression rule checks the cmb2 text domain before removing AdThrive_Ads callbacks, but CMB2 is a separate custom-fields toolkit. That rule does not reliably identify an active Raptive integration; verify your site's ads separately.
Cookie Consent Banners
| Plugin | Detection or hook |
|---|---|
| Cookiebot | cookiebot |
| Beautiful Cookie Consent Banner | bar-cookie-consent |
| Real Cookie Banner | real-cookie-banner |
| GDPR Cookie Consent | gdpr-cookie-consent |
| Pressidium Cookie Consent | pressidium-cookie-consent |
| WPConsent | wpconsent-cookies-banner-privacy-suite |
| GDPR Cookie Compliance | gdpr-cookie-compliance |
| Complianz | GDPR/CCPA Cookie Consent | complianz-gdpr |
| CookieYes | GDPR Cookie Consent | cookie-law-info |
| Cookie Notice & Compliance for GDPR / CCPA | cookie-notice |
| CookieAdmin | cookieadmin |
| Cookie Notice and Consent Banner | cookie-notice-and-consent-banner |
| Cookies and Content Security Policy | cookies-and-content-security-policy |
| Termly - GDPR/CCPA Cookie Consent Banner | uk-cookie-consent |
| GDPR by Trew Knowledge | gdpr |
| Cookie Consent Box by Supsystic | gdpr-cookie-consent-by-supsystic |
Marketing Dialogs & Popups
| Plugin | Detection or hook |
|---|---|
| Popup Maker | popup-maker |
| OptinMonster | optin-monster-api |
| Popup Maker WP | SGPM callbacks |
| Popup by Supsystic | popup-by-supsystic |
| PopupKit | popup-builder-block |
| Popup Anything - A Marketing Popup | popup-anything-on-click |
| Advanced Popups | advanced-popups |
| Popup Box | ays-popup-box |
| Depicter - Popup & Slider Builder | depicter |
| FooBox Image Lightbox | foobox-image-lightbox |
| MailOptin Lite | mailoptin |
| Poptin | poptin |
| Popup Builder | popup-builder |
| Popups for Divi | divi-popup |
| Hustle | hustle |
| WP Popups Lite | wp-popups-lite |
| Icegram Express | email-subscribers |
| Convert Pro | convertpro |
| Newsletter Popup | newsletter-popup |
| Ninja Popups | ninja-popups |
| Thrive Leads | Thrive_Leads class and tve_leads_should_display filter |
The Convert Pro rule checks convertpro and removes Convert_Plug callbacks. An additional cp_show_popup override is registered only when cp_v2_popup_enabled exists. Convert Pro and Convert Plus are separate products; this shared override does not establish full support for both.
Included subscription content
Explicitly selected posts and pages are supported through:
| Plugin | Content access hook |
|---|---|
| Paid Memberships Pro | pmpro_has_membership_access_filter |
| WP-Members | wpmem_block |
The adapters use the post supplied by the membership plugin, so including one post does not unlock another. See the Paid Memberships Pro hook reference and WP-Members hook reference.
Other paywalls and custom subscription functionality require a content-specific integration. Freedom access applies only to included content through supported integrations. It does not create a site subscription or purchase, or bypass post passwords. A selection alone cannot unlock an unsupported plugin.
Custom included access
Custom templates can use \ZeroAd\WP\Actions\SubscriptionAccess::allowsPost($post) when deciding whether to show the selected paid content. This checks the verified subscriber context and the publisher's selection. Preserve any access the visitor already has through your own membership system:
$canRead = $existingAccess || \ZeroAd\WP\Actions\SubscriptionAccess::allowsPost($post);
To maintain a custom content group instead of selecting each post, use the zeroad_freedom_includes_post filter. This example includes a publisher-created category:
add_filter('zeroad_freedom_includes_post', function ($included, $post) {
return $included || has_category('freedom-included', $post);
}, 10, 2);
The filter defines included content; it is not proof of membership. Use allowsPost() for the actual access decision. Private, draft, password-protected, and non-post/page content remains excluded. Map custom paid functionality separately in your application, without granting account-management or purchase permissions.
An already clean, unrestricted site needs only its Publisher ID. A paywalled site must configure and verify actual elevated access before treating the integration as complete. Clear page caches whenever the included selection or custom mapping changes.