Xenios Consent
Lightweight cookie consent management for WP Consent API and Google Consent Mode v2 through Google Tag Manager.
by Kostas Chasiotis · github.com/kostaschasiotis/xenios-consent · website
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/kostaschasiotis/xenios-consent/archive/refs/heads/main.zipLightweight WordPress consent management for the WP Consent API, Google Consent Mode v2, and Google Tag Manager.
Current version: 0.1.3
Author: Kostas Chasiotis
License: GPL-2.0-or-later
Xenios Consent is designed for a controlled tracking architecture in which Google Analytics, Google Ads, Meta Pixel, and other analytics or advertising scripts are deployed through a single Google Tag Manager container.
The plugin does not attempt to scan every cookie or automatically block arbitrary scripts inserted by themes and third-party plugins. The intended approach is simpler and more predictable:
- Xenios Consent sets the visitor's consent state before GTM loads.
- GTM loads once and receives the current Google Consent Mode v2 state.
- Analytics and advertising tags are configured to require the appropriate consent.
- Consent changes are pushed to both Google Consent Mode and the GTM
dataLayer. - Accessible first-party analytics and advertising cookies are removed when the corresponding consent is denied or withdrawn.
[!IMPORTANT] This plugin is a technical consent tool, not legal advice. The website owner remains responsible for the wording, categories, retention periods, privacy documentation, and compliance requirements applicable to the website.
Table of contents
- Features
- Requirements
- Architecture
- Consent categories
- Installation
- Plugin settings
- Frontend behavior
- Consent cookie and dataLayer
- WP Consent API integration
- Cookie cleanup
- Styling and CSS selectors
- GTM container template
- Importing the sample GTM JSON
- Ecommerce dataLayer requirements
- Manual GTM setup
- Google Analytics 4 setup
- Meta Pixel setup
- Google Ads setup
- Product identifier strategy
- Testing
- Troubleshooting
- Deployment checklist
- File structure
- Changelog
- Official references
Features
- Opt-in consent model.
- Four consent categories:
- Functional
- Preferences
- Statistics
- Marketing
- Functional consent is always enabled.
- Consent banner with:
- Manage preferences
- Reject optional
- Accept all
- Preferences modal with individual optional-category controls.
- Persistent Cookie settings button after a decision is made.
[xenios_consent_preferences]shortcode for placing a preferences button anywhere.- Native integration with WP Consent API.
- Google Consent Mode v2 defaults before GTM loads.
- Google Consent Mode updates after every consent change.
- Support for:
functionality_storagesecurity_storagepersonalization_storageanalytics_storagead_storagead_user_dataad_personalization
- Reusable
xenios_consent_updatedataLayer event. - Configurable GTM container ID.
- Configurable consent duration.
- Configurable consent version for requesting consent again after material changes.
- Defensive cleanup of accessible Google Analytics, Google Ads, and Meta cookies.
- Theme typography inherited by default.
- Plugin CSS enqueued early so theme or site CSS can override it without
!importantin normal circumstances. - Accessible modal behavior:
- Focus management
- Escape-to-close
- Focus trap
- Backdrop close
- ARIA labels and roles
Requirements
- WordPress
6.5or newer. - PHP
7.4or newer. - WP Consent API installed and active.
- A Google Tag Manager web container.
- All relevant analytics and advertising scripts should be managed through that GTM container.
For the included ecommerce GTM template, you also need either:
- Xenios WooCommerce DataLayer
1.1.2or newer, or - Another implementation that pushes the same GA4-style ecommerce structure documented below.
Architecture
The plugin prints a small bootstrap script in wp_head at priority 0.
Before GTM is loaded, the bootstrap:
- Reads and validates the
xenios_consentcookie. - Creates the current category state.
- Sets
window.wp_consent_typetooptin. - Pushes the consent state into
dataLayer. - defines
gtag()as a dataLayer wrapper. - Sends the Google Consent Mode v2 default state.
- Removes accessible cookies that conflict with the denied categories.
- Loads the configured GTM container.
The recommended GTM implementation is intentionally strict:
- GTM itself loads before consent.
- Consent defaults are available before GTM starts.
- Analytics and advertising tags have explicit additional consent requirements.
- Those tags do not execute while their required consent is denied.
- When consent is granted, the
xenios_consent_updateevent can initialize the corresponding platform immediately without requiring a page reload.
This produces Basic Consent Mode behavior for measurement tags while still keeping the consent state available to GTM from the beginning of the page lifecycle.
Consent categories
| Xenios category | WP Consent API | Google consent signals | Purpose |
|---|---|---|---|
| Functional | functional |
functionality_storage, security_storage |
Core website behavior and consent storage |
| Preferences | preferences |
personalization_storage |
Non-essential personalization and remembered preferences |
| Statistics | statistics, statistics-anonymous |
analytics_storage |
Analytics, performance, and usage measurement |
| Marketing | marketing |
ad_storage, ad_user_data, ad_personalization |
Advertising, remarketing, conversion measurement, and ad personalization |
Functional storage and security storage are always granted.
All optional categories default to denied until a valid stored decision exists.
Installation
- Install and activate WP Consent API.
- Upload the
xenios-consentplugin folder to/wp-content/plugins/, or install its ZIP through Plugins → Add New → Upload Plugin. - Activate Xenios Consent.
- Open Settings → Xenios Consent.
- Enable consent management.
- Enter the GTM container ID in
GTM-XXXXXXXformat. - Select the cookie or privacy policy page.
- Adjust the banner title and text.
- Save the settings.
- Configure the GTM container using the included template or the manual guide below.
- Remove or disable any other WordPress plugin or theme integration that independently injects GA4, Google Ads, Meta Pixel, or the same GTM container.
[!WARNING] Do not insert the same GTM container through both Xenios Consent and another plugin, theme option, or hard-coded snippet. Loading GTM twice can duplicate tags and ecommerce events.
Plugin settings
The settings page is available under Settings → Xenios Consent.
Enable consent management
Controls whether the banner, consent bootstrap, and GTM loader are active.
GTM container ID
Accepts a value in this format:
GTM-XXXXXXX
Tracking scripts should be deployed through this container rather than directly through WordPress plugins or theme code.
Banner title
Default:
Your privacy matters
Banner text
Default:
We use cookies and similar technologies for preferences, statistics and marketing. You can accept all, reject optional cookies or choose your preferences.
Cookie or privacy policy
Selects a WordPress page. A Read our policy link is added to the banner when a page is selected.
Consent duration
The number of days for which the visitor's choice is stored.
- Default:
180 - Minimum:
1 - Maximum:
730
Configuration version
Default: 1
Increase this integer whenever the purposes, categories, vendors, or consent wording materially change. A stored consent cookie with an older version is treated as invalid and the visitor is asked again.
Frontend behavior
First visit
- The banner is displayed.
- Functional consent is active.
- Preferences, Statistics, and Marketing are denied.
- Consent Mode v2 defaults are pushed before GTM loads.
Accept all
Grants:
- Preferences
- Statistics
- Marketing
Reject optional
Keeps Functional enabled and denies all optional categories.
Manage preferences
Opens the preferences modal, allowing the visitor to enable or disable each optional category.
Existing valid consent
- The banner is hidden.
- The fixed Cookie settings button is displayed.
- The saved consent state is applied before GTM loads.
Changing consent
After a new decision or update:
- The consent cookie is rewritten.
- WP Consent API is updated.
- Google Consent Mode is updated.
- Denied-category cookies are cleaned up.
xenios_consent_updateis pushed to the dataLayer.- The fixed preferences button remains available.
Shortcode
Place this shortcode in a page, footer, template, or widget:
[xenios_consent_preferences]
It renders a button that opens the same preferences modal.
Consent cookie and dataLayer
Consent cookie
Cookie name:
xenios_consent
Example decoded value:
{
"version": 1,
"decided": true,
"timestamp": 1784190000,
"categories": {
"functional": true,
"preferences": false,
"statistics": true,
"marketing": false
}
}
Cookie properties:
- Path:
/ - SameSite:
Lax - Secure: enabled on HTTPS
- Expiration: configured in the plugin settings
Initial dataLayer state
Before GTM loads, the plugin pushes an object similar to:
{
xenios_consent: {
functional: true,
preferences: false,
statistics: false,
marketing: false
},
xenios_consent_decided: false,
xenios_consent_version: 1
}
Consent update event
After a visitor saves or changes consent:
{
event: 'xenios_consent_update',
xenios_consent: {
functional: true,
preferences: true,
statistics: true,
marketing: false
},
xenios_consent_decided: true,
xenios_consent_version: 1
}
The included GTM template reads:
xenios_consent.statistics
xenios_consent.marketing
These values are Boolean true or false.
WP Consent API integration
Xenios Consent:
- Declares the website consent type as
optin. - Registers itself as compliant with the WP Consent API.
- Registers the
xenios_consentcookie as a Functional cookie. - Synchronizes these consent categories:
functionalpreferencesstatisticsstatistics-anonymousmarketing
- Uses the configured Xenios Consent duration for the WP Consent API cookie expiration filter.
Compatible WordPress plugins can read the standardized consent state through WP Consent API functions.
Cookie cleanup
When Statistics consent is denied, the plugin attempts to remove accessible first-party cookies matching:
_ga
_ga_*
_gid
_gat
_gat_*
When Marketing consent is denied, it attempts to remove:
_gcl_*
_gac_*
_fbp
_fbc
It also removes the standard Google Conversion Linker storage key:
_gcl_ls
from localStorage and sessionStorage when accessible.
Cleanup runs:
- Before GTM loads when the saved state denies the category.
- Immediately after consent is rejected or withdrawn.
Cleanup limitations
JavaScript can only remove storage that is accessible to the current page. It cannot remove:
HttpOnlycookies.- Cookies on unrelated domains.
- Storage blocked by the browser.
- Cookies that are immediately recreated by a script loaded outside the consent architecture.
This is why tracking scripts should not be independently injected by other plugins or theme code.
Styling and CSS selectors
The plugin stylesheet is enqueued on wp_enqueue_scripts at priority 5, allowing the normal theme stylesheet and site-specific CSS to override the defaults afterward.
Typography inherits the active theme font by default.
Main selectors
/* Root */
.xenios-consent {}
/* Banner */
.xenios-consent__banner {}
.xenios-consent__content {}
.xenios-consent__actions {}
/* Buttons */
.xenios-consent__button {}
.xenios-consent__button--primary {}
.xenios-consent__button--secondary {}
.xenios-consent__reopen {}
.xenios-consent-preferences-link {}
/* Modal */
.xenios-consent__backdrop {}
.xenios-consent__dialog {}
.xenios-consent__dialog-header {}
.xenios-consent__dialog-actions {}
.xenios-consent__close {}
/* Categories */
.xenios-consent__categories {}
.xenios-consent__category {}
.xenios-consent__category span {}
.xenios-consent__category small {}
.xenios-consent__category input {}
/* Applied to the document while the modal is open */
.xenios-consent-is-open {}
Current base layout
.xenios-consent__banner {
align-items: end;
}
.xenios-consent__content {
max-width: 640px;
}
At viewport widths up to 760px, the banner changes to a vertical layout and the buttons stretch to full width.
Example theme override
.xenios-consent__banner,
.xenios-consent__dialog {
background: #f7f4ef;
color: #28231f;
border-radius: 0;
}
.xenios-consent__button--primary,
.xenios-consent__reopen {
background: #28231f;
border-color: #28231f;
color: #fff;
}
.xenios-consent__button--secondary,
.xenios-consent-preferences-link {
background: transparent;
border-color: #28231f;
color: #28231f;
}
GTM container template
A sanitized reusable GTM export is included at:
examples/gtm-container-template.json
The template contains:
- 9 tags
- 5 triggers
- 20 user-defined variables
- The official Meta Pixel Community Template
- GA4 pageview and ecommerce tracking
- Meta PageView and ecommerce tracking
- Meta consent revocation
- Google Ads base tag
- Google Ads standard remarketing
- Google Ads dynamic ecommerce remarketing
- A paused Google Ads Purchase conversion tag
All account-specific identifiers have been replaced with placeholders.
Template assumptions
The sample container expects these ecommerce events:
view_item
add_to_cart
begin_checkout
purchase
It expects the ecommerce data under:
ecommerce
It also expects Xenios Consent to push:
xenios_consent_update
xenios_consent.statistics
xenios_consent.marketing
Importing the sample GTM JSON
- Create or open a GTM Web container.
- Open Admin → Import Container.
- Choose
examples/gtm-container-template.json. - Select an existing workspace or create a dedicated import workspace.
- For a new or empty container, choose Merge.
- Review the import summary.
- Confirm that the Meta Pixel custom template is included.
- Accept the requested template permissions after reviewing them.
- Complete the import.
- Do not publish yet.
- Replace every placeholder constant listed below.
- Preview and test all consent states.
- Publish only after the complete validation checklist passes.
Required constants
| Variable | Required format | Example placeholder |
|---|---|---|
CONST - GA4 Measurement ID |
Full GA4 Measurement ID | G-XXXXXXXXXX |
CONST - Google Ads Tag ID |
Full Google Ads tag ID including AW- |
AW-00000000000 |
CONST - Google Ads Conversion ID |
Numeric ID only | 00000000000 |
CONST - Google Ads Purchase Label |
Conversion label only | REPLACE_WITH_PURCHASE_LABEL |
CONST - Meta Pixel ID |
Numeric Pixel ID | 000000000000000 |
CONST - Google Ads Product ID Field |
item_group_id or item_id |
item_group_id |
[!CAUTION] The Google Ads Purchase conversion tag remains paused in the sample. Keep it paused until the real Conversion Label is entered and the tag has been tested.
Google Ads ID distinction
The two Google Ads variables intentionally use different formats:
Google Tag ID: AW-12345678901
Conversion ID: 12345678901
Using the full AW-... value inside the Google Ads Remarketing or Conversion Tracking tag can produce an invalid AW-AW-... result.
Ecommerce dataLayer requirements
The included GTM setup is designed for Xenios WooCommerce DataLayer 1.1.2 or newer.
The companion plugin does not load GTM, GA4, Meta Pixel, Google Ads, or consent scripts. It only pushes standardized WooCommerce ecommerce events.
Supported events
| WooCommerce action | dataLayer event |
|---|---|
| Product detail viewed | view_item |
| Product added to cart | add_to_cart |
| Checkout opened | begin_checkout |
| Valid order completed/received | purchase |
Canonical event example
dataLayer.push({
event: 'purchase',
event_id: 'purchase_123',
ecommerce: {
transaction_id: '123',
value: 120,
currency: 'EUR',
tax: 20,
shipping: 5,
coupon: 'WELCOME10',
affiliation: 'Store name',
items: [
{
item_id: '456',
item_group_id: '400',
item_name: 'Product name',
price: 120,
quantity: 1,
item_category: 'Jewelry',
item_category2: 'Rings',
item_variant: 'Size 54'
}
]
}
});
Variable products
The default companion-plugin behavior is:
item_id: selected WooCommerce variation ID.item_group_id: parent variable-product ID.- Simple products:
item_idcontains the product ID anditem_group_idmay be absent.
This gives GA4 the most specific purchased/viewed item while allowing advertising platforms to use either the variation or parent identifier according to their product feed.
Purchase validation and deduplication
The companion DataLayer plugin:
- Supports the standard WooCommerce order-received route.
- Supports validated custom thank-you URLs containing a matching
keyandorder_id. - Validates the WooCommerce order key.
- Emits Purchase only for approved order statuses.
- Excludes pending, failed, cancelled, refunded, and deleted orders by default.
- Uses browser storage to suppress repeated Purchase pushes from the same browser for 90 days.
- Uses
ecommerce.transaction_idfor platform-side deduplication. - Uses top-level
event_id, such aspurchase_123, for possible future Meta browser/server deduplication. - Does not place customer names, email addresses, phone numbers, or postal addresses in the dataLayer.
Manual GTM setup
The following sections reproduce the configuration contained in the sample JSON.
Built-in variables
Enable:
- Page URL
- Page Hostname
- Page Path
- Referrer
- Event
Constant variables
Create:
CONST - GA4 Measurement ID
CONST - Google Ads Tag ID
CONST - Google Ads Conversion ID
CONST - Google Ads Purchase Label
CONST - Google Ads Product ID Field
CONST - Meta Pixel ID
Recommended initial value for CONST - Google Ads Product ID Field:
item_group_id
Change it to item_id when Merchant Center uses individual variation IDs.
Data Layer Variables
Create all variables with Data Layer Version 2:
| Variable name | Data layer key |
|---|---|
DLV - Ecommerce Items |
ecommerce.items |
DLV - Ecommerce Value |
ecommerce.value |
DLV - Ecommerce Currency |
ecommerce.currency |
DLV - Ecommerce Transaction ID |
ecommerce.transaction_id |
DLV - Ecommerce Tax |
ecommerce.tax |
DLV - Ecommerce Shipping |
ecommerce.shipping |
DLV - Ecommerce Coupon |
ecommerce.coupon |
DLV - Ecommerce Affiliation |
ecommerce.affiliation |
DLV - Event ID |
event_id |
DLV - Consent Statistics |
xenios_consent.statistics |
DLV - Consent Marketing |
xenios_consent.marketing |
Do not set artificial defaults for the ecommerce variables. An absent value should remain undefined rather than becoming an inaccurate zero or empty string.
Ecommerce trigger
Create a Custom Event trigger:
Name: CE - Ecommerce - All
Event name: ^(view_item|add_to_cart|begin_checkout|purchase)$
Use regex matching: enabled
Purchase trigger
Create a Custom Event trigger:
Name: CE - purchase
Event name: purchase
This is used by the native Google Ads Purchase conversion tag.
Statistics granted trigger
Name: CE - Consent Statistics Granted
Type: Custom Event
Event name: xenios_consent_update
Condition: {{DLV - Consent Statistics}} equals true
Marketing granted trigger
Name: CE - Consent Marketing Granted
Type: Custom Event
Event name: xenios_consent_update
Condition: {{DLV - Consent Marketing}} equals true
Marketing revoked trigger
Name: CE - Consent Marketing Revoked
Type: Custom Event
Event name: xenios_consent_update
Condition: {{DLV - Consent Marketing}} equals false
Google Analytics 4 setup
Google's current ecommerce model uses recommended events such as view_item, add_to_cart, begin_checkout, and purchase, with product data inside an items array.
1. GA4 Google Tag
Name: Google Tag - GA4
Tag type: Google Tag
Tag ID: {{CONST - GA4 Measurement ID}}
Triggers:
Initialization - All Pages
CE - Consent Statistics Granted
Firing option:
Once per page
Additional consent requirement:
analytics_storage
The Google Tag sends the normal pageview after it initializes. A separate GA4 PageView tag is not required in this setup.
Why both triggers are used:
- If Statistics consent is already saved, the tag fires during Initialization.
- If the visitor grants Statistics during the current page, it fires on
xenios_consent_updatewithout requiring a reload. Once per pageprevents duplicate initialization if both trigger paths become eligible.
2. GA4 ecommerce events tag
Name: GA4 - Ecommerce Events
Tag type: Google Analytics: GA4 Event
Measurement ID override: {{CONST - GA4 Measurement ID}}
Event name: {{Event}}
Event parameters:
| Parameter | Value |
|---|---|
currency |
{{DLV - Ecommerce Currency}} |
value |
{{DLV - Ecommerce Value}} |
items |
{{DLV - Ecommerce Items}} |
transaction_id |
{{DLV - Ecommerce Transaction ID}} |
tax |
{{DLV - Ecommerce Tax}} |
shipping |
{{DLV - Ecommerce Shipping}} |
coupon |
{{DLV - Ecommerce Coupon}} |
affiliation |
{{DLV - Ecommerce Affiliation}} |
Trigger:
CE - Ecommerce - All
Firing option:
Once per event
Additional consent requirement:
analytics_storage
GA4 validation
In GTM Preview and GA4 DebugView, confirm:
view_itemcontains the viewed product.add_to_cartcontains the selected variation and quantity.begin_checkoutcontains all cart items and checkout value.purchasecontains:- Unique
transaction_id - Correct
value - Correct ISO currency
- Correct items
- Tax and shipping where applicable
- Unique
Meta Pixel setup
The sample container includes Meta's Community Template Gallery tag from the official Meta GitHub repository.
1. Install the Meta template manually when needed
If it was not included during import:
- Open Templates → Tag Templates.
- Click Search Gallery.
- Search for Meta Pixel or Facebook Pixel.
- Select the template published by Meta/Facebook.
- Review and accept its permissions.
2. Meta event-name lookup table
Create:
Name: LUT - Meta Event Name
Input: {{Event}}
Mappings:
| Input | Output |
|---|---|
view_item |
ViewContent |
add_to_cart |
AddToCart |
begin_checkout |
InitiateCheckout |
purchase |
Purchase |
3. Meta event properties variable
Create a Custom JavaScript variable:
Name: CJS - Meta Event Properties
function() {
var items = {{DLV - Ecommerce Items}} || [];
var contents = items
.filter(function(item) {
return item && item.item_id;
})
.map(function(item) {
return {
id: String(item.item_group_id || item.item_id),
quantity: Number(item.quantity || 1),
item_price: Number(item.price || 0)
};
});
var contentIds = contents.map(function(item) {
return item.id;
});
var quantity = contents.reduce(function(total, item) {
return total + item.quantity;
}, 0);
return {
content_ids: contentIds,
contents: contents,
content_type: 'product',
value: Number({{DLV - Ecommerce Value}} || 0),
currency: '{{DLV - Ecommerce Currency}}',
num_items: quantity
};
}
This implementation assumes the Meta catalog uses parent WooCommerce product IDs for variable products. It falls back to item_id for simple products or when item_group_id is unavailable.
If the Meta catalog lists every variation separately, replace:
item.item_group_id || item.item_id
with:
item.item_id
4. Meta base Pixel and PageView tag
Name: OFFICIAL Meta - Base Pixel + PageView
Tag type: Meta Pixel community template
Pixel ID: {{CONST - Meta Pixel ID}}
Event type: Standard
Standard event: PageView
Consent Granted: true
Recommended options:
Disable Automatic Configuration: enabled
Disable History Event Tracking: enabled
Advanced Matching: disabled
Meta-enabled Conversions API: disabled
Triggers:
Initialization - All Pages
CE - Consent Marketing Granted
Firing option:
Once per page
Additional consent requirement:
ad_storage
5. Meta ecommerce events tag
Name: OFFICIAL Meta - Ecommerce Events
Tag type: Meta Pixel community template
Pixel ID: {{CONST - Meta Pixel ID}}
Event name type: Variable
Event name: {{LUT - Meta Event Name}}
Load Properties From Variable: {{CJS - Meta Event Properties}}
Event ID: {{DLV - Event ID}}
Consent Granted: true
Recommended options:
Disable Automatic Configuration: enabled
Disable History Event Tracking: enabled
Advanced Matching: disabled
Meta-enabled Conversions API: disabled
Trigger:
CE - Ecommerce - All
Firing option:
Once per event
Additional consent requirement:
ad_storage
Tag sequencing:
Fire a setup tag before this tag:
OFFICIAL Meta - Base Pixel + PageView
Do not fire the ecommerce tag if the setup tag fails:
enabled
6. Meta consent revocation tag
Create a Custom HTML tag:
Name: Meta - Revoke Consent
<script>
if (typeof window.fbq === 'function') {
window.fbq('consent', 'revoke');
}
</script>
Trigger:
CE - Consent Marketing Revoked
Firing option:
Once per event
No additional consent requirement should be added. The tag must be allowed to revoke Meta consent after Marketing is withdrawn.
7. Meta Traffic Permissions
If the tag fires correctly but Meta rejects the request with a message that the Pixel is unavailable on the website, check:
Meta Events Manager → Data Sources → Pixel → Settings → Traffic Permissions
When an allow list is active, add both the root and www hostname where applicable.
Also confirm that the domain is not present in a block list.
Google Ads setup
1. Google Ads Google Tag
Name: Google Tag - Google Ads
Tag type: Google Tag
Tag ID: {{CONST - Google Ads Tag ID}}
The Tag ID must include the AW- prefix.
Triggers:
Initialization - All Pages
CE - Consent Marketing Granted
Firing option:
Once per page
Additional consent requirements:
ad_storage
ad_user_data
ad_personalization
2. Standard remarketing tag
Name: Google Ads - Remarketing - All Pages
Tag type: Google Ads Remarketing
Conversion ID: {{CONST - Google Ads Conversion ID}}
Conversion Label: empty unless a tag-based list specifically requires one
Dynamic Remarketing: disabled
The Conversion ID is numeric and must not include AW-.
Triggers:
Initialization - All Pages
CE - Consent Marketing Granted
Firing option:
Once per page
Additional consent requirements:
ad_storage
ad_user_data
ad_personalization
Tag sequencing:
Setup tag: Google Tag - Google Ads
Do not fire if setup tag fails: enabled
3. Google Ads dynamic remarketing items variable
Create:
Name: CJS - Google Ads Remarketing Items
Type: Custom JavaScript
function() {
var items = {{DLV - Ecommerce Items}} || [];
var idField = '{{CONST - Google Ads Product ID Field}}';
var seen = {};
return items.reduce(function(result, item) {
if (!item) {
return result;
}
var id;
if (idField === 'item_group_id') {
id = item.item_group_id || item.item_id;
} else {
id = item.item_id;
}
if (!id) {
return result;
}
id = String(id);
if (seen[id]) {
return result;
}
seen[id] = true;
result.push({
id: id,
google_business_vertical: 'retail'
});
return result;
}, []);
}
The variable deduplicates repeated product IDs, which is useful when multiple variations map to one parent feed item.
4. Dynamic ecommerce remarketing tag
Name: Google Ads - Dynamic Remarketing - Ecommerce
Tag type: Google Ads Remarketing
Conversion ID: {{CONST - Google Ads Conversion ID}}
Dynamic Remarketing: enabled
Event Name: {{Event}}
Event Value: {{DLV - Ecommerce Value}}
Event Items: {{CJS - Google Ads Remarketing Items}}
Trigger:
CE - Ecommerce - All
Firing option:
Once per event
Additional consent requirements:
ad_storage
ad_user_data
ad_personalization
Tag sequencing:
Setup tag: Google Tag - Google Ads
Do not fire if setup tag fails: enabled
5. Native Google Ads Purchase conversion
The sample includes this tag in a paused state:
Name: Google Ads - Conversion - Purchase
Tag type: Google Ads Conversion Tracking
Conversion ID: {{CONST - Google Ads Conversion ID}}
Conversion Label: {{CONST - Google Ads Purchase Label}}
Conversion Value: {{DLV - Ecommerce Value}}
Currency Code: {{DLV - Ecommerce Currency}}
Transaction ID: {{DLV - Ecommerce Transaction ID}}
Trigger:
CE - purchase
Firing option:
Once per event
Additional consent requirements:
ad_storage
ad_user_data
ad_personalization
Tag sequencing:
Setup tag: Google Tag - Google Ads
Do not fire if setup tag fails: enabled
Keep this tag paused until the Conversion Label is available.
To find the label:
Google Ads → Goals → Conversions → Summary
→ Select the Purchase conversion
→ Tag setup
→ Use Google Tag Manager
The full destination may look like:
AW-12345678901/AbCdEfGhIjKlMn
Store only the part after / in the Purchase Label constant:
AbCdEfGhIjKlMn
Native Google Ads Purchase versus imported GA4 Purchase
Choose one Purchase conversion as the primary bidding conversion:
- Native Google Ads Purchase tag, or
- Imported GA4
purchaseconversion.
Do not mark both as primary for the same purchase action unless the advertising strategy intentionally accounts for the resulting duplicate conversion reporting.
Conversion Linker
The template does not include a separate Conversion Linker tag. The Google Tag loads on every eligible page, and the Google Ads tags have their built-in conversion-linker option enabled.
Product identifier strategy
Product identifiers sent by remarketing tags must exactly match the identifiers used by the corresponding catalog or feed.
GA4
The recommended WooCommerce mapping is:
item_id = variation ID for selected variations
item_group_id = parent variable-product ID
Simple products use their normal product ID.
Meta
The included CJS - Meta Event Properties uses:
item.item_group_id || item.item_id
Therefore:
- Variable product → parent product ID
- Simple product → product ID
This matches a Meta catalog that contains parent WooCommerce product IDs.
Google Ads / Merchant Center
The constant:
CONST - Google Ads Product ID Field
controls the choice:
item_group_id
or:
item_id
Use item_group_id only when Merchant Center product IDs correspond to parent WooCommerce products.
Use item_id when Merchant Center contains individual variation IDs.
Checking Merchant Center
Open:
Google Merchant Center → Products → All products
Inspect a variable product and compare:
- Merchant Center ID
- Merchant Center Item group ID
- WooCommerce parent product ID
- WooCommerce variation ID
Common patterns:
| Merchant Center behavior | Recommended GTM field |
|---|---|
| Each variation has its own ID | item_id |
| Only the parent product is present | item_group_id |
IDs use prefixes such as gla_, woocommerce_, or a country code |
Modify the DataLayer ID format or GTM transformation to reproduce the complete feed ID |
Example comparison:
WooCommerce parent ID: 2948
WooCommerce variation ID: 2949
Merchant Center ID: 2949
Merchant Center item group: 2948
In this example, use item_id if dynamic remarketing should match the variation-level Merchant Center item.
Testing
Always test in a fresh private/incognito browser session and clear existing site storage between major test cases.
GTM Preview test matrix
| State | GA4 | Google Ads | Meta |
|---|---|---|---|
| No decision | Must not fire | Must not fire | Must not fire |
| Reject optional | Must not fire | Must not fire | Must not fire |
| Statistics only | May fire | Must not fire | Must not fire |
| Marketing only | Must not fire | May fire | May fire |
| Accept all | May fire | May fire | May fire |
| Revoke Statistics | Future GA4 tags blocked; analytics cookies cleaned | Unchanged | Unchanged |
| Revoke Marketing | Unchanged | Future Ads tags blocked; ad cookies/storage cleaned | fbq('consent', 'revoke'); future tags blocked |
Consent checks
In Tag Assistant, confirm:
- Initial consent defaults appear before Google tags.
analytics_storagefollows Statistics.ad_storage,ad_user_data, andad_personalizationfollow Marketing.- Tags show Not fired due to missing consent before the required category is granted.
- Granting consent triggers the correct base tag on the same page.
- Re-saving preferences does not duplicate base tags on the same page.
Network checks
Before consent, verify that there are no measurement requests to the relevant endpoints for blocked platforms.
After consent, verify that the expected requests appear.
Ecommerce checks
Test the full path:
- View a product.
- Add a simple product or a selected variation to cart.
- Open checkout.
- Complete a test order.
- Inspect every dataLayer event.
- Confirm the correct platform tags fire.
- Confirm product IDs match the relevant catalog.
- Refresh the thank-you page and confirm Purchase is not sent again by the dataLayer plugin.
- Confirm GA4 and Google Ads use the order ID as the transaction ID.
- Confirm Meta receives the top-level Event ID.
Troubleshooting
Theme CSS requires !important
The plugin stylesheet should load early at priority 5. Confirm that:
- The site is running Xenios Consent
0.1.2or newer. - The theme stylesheet loads afterward.
- A cache or optimization plugin is not reordering CSS unexpectedly.
Wrong font in buttons or modal
The plugin uses inherited typography. Form controls are explicitly set to the inherited font. Clear all CSS caches after updating.
Analytics or advertising tag does not fire
Check:
- The expected ecommerce event exists in the dataLayer.
- The correct consent category is
true. - The tag has the appropriate additional consent requirement.
- The trigger uses Boolean
true, not the textallow. - The relevant base tag can fire on the consent-granted event.
Google Ads shows AW-AW-...
The Google Ads Remarketing and Conversion templates require the numeric Conversion ID:
12345678901
Only the Google Tag uses:
AW-12345678901
Meta tag fires but no event reaches Meta
Check:
- Meta Events Manager Traffic Permissions.
- The root and
wwwhostnames. - Pixel ID constant.
- Browser console errors.
- Meta Pixel Helper.
- Ad blockers and browser privacy protections.
Meta products do not match the catalog
The sample sends parent IDs for variable products. Confirm whether the Meta catalog contains parent IDs or variation IDs and adjust CJS - Meta Event Properties when necessary.
Google Ads dynamic remarketing products do not match Merchant Center
Confirm:
CONST - Google Ads Product ID Fieldis correct.- Prefixes in Merchant Center IDs are also present in the data sent by GTM.
google_business_verticalisretail.- Every product ID is sent as a string.
Purchase does not fire on a custom thank-you page
Use Xenios WooCommerce DataLayer 1.1.2 or newer. The custom route must contain:
- A valid WooCommerce order key.
- The matching
order_id. - An eligible order status.
Purchase fires more than once
Confirm:
- The dataLayer plugin's browser deduplication is active.
- GA4 and Google Ads receive a stable
transaction_id. - Only one Purchase dataLayer implementation exists.
- No separate ecommerce plugin is also sending Purchase.
Google Ads Purchase tag is not firing
The sample tag is deliberately paused. Before enabling it:
- Enter the real Purchase Conversion Label.
- Confirm the Conversion ID.
- Confirm Marketing consent.
- Test a new order in Preview.
- Verify value, currency, and transaction ID.
- Unpause and publish.
Deployment checklist
WordPress
- [ ] WP Consent API installed and active.
- [ ] Xenios Consent installed and active.
- [ ] Correct GTM container ID saved.
- [ ] Cookie/privacy policy page selected.
- [ ] Consent duration reviewed.
- [ ] Configuration version reviewed.
- [ ] No duplicate GTM injection.
- [ ] No independent GA4, Google Ads, or Meta scripts.
- [ ] Xenios WooCommerce DataLayer installed for ecommerce sites.
GTM
- [ ] All placeholder constants replaced.
- [ ] GA4 base tag set to Once per page.
- [ ] Meta and Google Ads base tags set to Once per page.
- [ ] Ecommerce tags set to Once per event.
- [ ] Consent requirements reviewed on every tag.
- [ ] Consent-granted triggers present on base tags.
- [ ] Meta revoke tag has no blocking consent requirement.
- [ ] Setup-tag failure protection enabled for Meta and Google Ads ecommerce tags.
- [ ] Google Ads Purchase remains paused until configured.
- [ ] Product ID strategy matches each product feed.
Platforms
- [ ] GA4 Measurement ID confirmed.
- [ ] GA4 DebugView validated.
- [ ] Google Ads Tag ID and numeric Conversion ID confirmed.
- [ ] Google Ads Purchase Label confirmed before unpausing.
- [ ] Exactly one Purchase conversion source chosen as primary.
- [ ] Merchant Center identifiers checked.
- [ ] Meta Pixel ID confirmed.
- [ ] Meta Traffic Permissions checked.
- [ ] Meta catalog identifiers checked.
Consent validation
- [ ] No decision tested.
- [ ] Reject optional tested.
- [ ] Statistics only tested.
- [ ] Marketing only tested.
- [ ] Accept all tested.
- [ ] Statistics withdrawal tested.
- [ ] Marketing withdrawal tested.
- [ ] Cookie cleanup tested.
- [ ] Consent persists across pages.
- [ ] Configuration-version reset tested when appropriate.
File structure
xenios-consent/
├── assets/
│ ├── css/
│ │ └── This README is longer than the copy stored here. Read the rest on GitHub →