WP Manifestindependent plugin directory
manifest / privacy / xenios-consent

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

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/kostaschasiotis/xenios-consent/archive/refs/heads/main.zip

Lightweight 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:

  1. Xenios Consent sets the visitor's consent state before GTM loads.
  2. GTM loads once and receives the current Google Consent Mode v2 state.
  3. Analytics and advertising tags are configured to require the appropriate consent.
  4. Consent changes are pushed to both Google Consent Mode and the GTM dataLayer.
  5. 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

  • 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_storage
    • security_storage
    • personalization_storage
    • analytics_storage
    • ad_storage
    • ad_user_data
    • ad_personalization
  • Reusable xenios_consent_update dataLayer 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 !important in normal circumstances.
  • Accessible modal behavior:
    • Focus management
    • Escape-to-close
    • Focus trap
    • Backdrop close
    • ARIA labels and roles

Requirements

  • WordPress 6.5 or newer.
  • PHP 7.4 or 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.2 or 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:

  1. Reads and validates the xenios_consent cookie.
  2. Creates the current category state.
  3. Sets window.wp_consent_type to optin.
  4. Pushes the consent state into dataLayer.
  5. defines gtag() as a dataLayer wrapper.
  6. Sends the Google Consent Mode v2 default state.
  7. Removes accessible cookies that conflict with the denied categories.
  8. 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_update event 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.


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

  1. Install and activate WP Consent API.
  2. Upload the xenios-consent plugin folder to /wp-content/plugins/, or install its ZIP through Plugins → Add New → Upload Plugin.
  3. Activate Xenios Consent.
  4. Open Settings → Xenios Consent.
  5. Enable consent management.
  6. Enter the GTM container ID in GTM-XXXXXXX format.
  7. Select the cookie or privacy policy page.
  8. Adjust the banner title and text.
  9. Save the settings.
  10. Configure the GTM container using the included template or the manual guide below.
  11. 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_update is 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

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.


Xenios Consent:

  • Declares the website consent type as optin.
  • Registers itself as compliant with the WP Consent API.
  • Registers the xenios_consent cookie as a Functional cookie.
  • Synchronizes these consent categories:
    • functional
    • preferences
    • statistics
    • statistics-anonymous
    • marketing
  • 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.


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:

  • HttpOnly cookies.
  • 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

  1. Create or open a GTM Web container.
  2. Open Admin → Import Container.
  3. Choose examples/gtm-container-template.json.
  4. Select an existing workspace or create a dedicated import workspace.
  5. For a new or empty container, choose Merge.
  6. Review the import summary.
  7. Confirm that the Meta Pixel custom template is included.
  8. Accept the requested template permissions after reviewing them.
  9. Complete the import.
  10. Do not publish yet.
  11. Replace every placeholder constant listed below.
  12. Preview and test all consent states.
  13. 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_id contains the product ID and item_group_id may 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 key and order_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_id for platform-side deduplication.
  • Uses top-level event_id, such as purchase_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_update without requiring a reload.
  • Once per page prevents 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_item contains the viewed product.
  • add_to_cart contains the selected variation and quantity.
  • begin_checkout contains all cart items and checkout value.
  • purchase contains:
    • Unique transaction_id
    • Correct value
    • Correct ISO currency
    • Correct items
    • Tax and shipping where applicable

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:

  1. Open Templates → Tag Templates.
  2. Click Search Gallery.
  3. Search for Meta Pixel or Facebook Pixel.
  4. Select the template published by Meta/Facebook.
  5. 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 purchase conversion.

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_storage follows Statistics.
  • ad_storage, ad_user_data, and ad_personalization follow 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:

  1. View a product.
  2. Add a simple product or a selected variation to cart.
  3. Open checkout.
  4. Complete a test order.
  5. Inspect every dataLayer event.
  6. Confirm the correct platform tags fire.
  7. Confirm product IDs match the relevant catalog.
  8. Refresh the thank-you page and confirm Purchase is not sent again by the dataLayer plugin.
  9. Confirm GA4 and Google Ads use the order ID as the transaction ID.
  10. 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.2 or 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 text allow.
  • 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 www hostnames.
  • 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 Field is correct.
  • Prefixes in Merchant Center IDs are also present in the data sent by GTM.
  • google_business_vertical is retail.
  • 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:

  1. Enter the real Purchase Conversion Label.
  2. Confirm the Conversion ID.
  3. Confirm Marketing consent.
  4. Test a new order in Preview.
  5. Verify value, currency, and transaction ID.
  6. 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 →