A complete reverse-engineered measurement framework derived from real GA4 BigQuery export data. Every configuration decision is backed by evidence from the dataset.
This booklet is built by working backwards from GA4's BigQuery export — reading what the analytics platform recorded to reconstruct the GTM container, dataLayer schema, and measurement plan that produced those records.
GA4 writes everything to BigQuery. Every event, every parameter, every session attribute. By querying bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_* we can infer:
clean_event parameter value 'gtm.js' is GTM's container fingerprint, present on every custom event.page_location and page_title reveal every page type.privacy_info.analytics_storage reveals if Consent Mode was active.gclid (Google Ads), dclid (Display), gclsrc (attribution source) confirm which Google media was active.The event_param key clean_event with value 'gtm.js' appears on page_view, user_engagement, scroll, view_item, view_promotion, begin_checkout, add_shipping_info, add_payment_info, and view_search_results. This string is injected by the GTM container initialization — it is not a GA4 built-in parameter. GTM was the tag management system.
| Source | Size | Period | Usage |
|---|---|---|---|
bquxjob_5695ae91_19ecca06cae.csv | ~1,000 rows | Nov–Dec 2020 | Event + parameter analysis, page taxonomy |
bquxjob_749b3c99_19ecc9d605c.csv | ~1,000 rows | Dec 2020–Jan 2021 | Traffic sources, device, country, ecommerce |
bigquery-public-data.ga4_obfuscated_sample_ecommerce | ~848K events | Nov 2020 – Jan 2021 | Full dataset referenced for aggregate metrics |
Each reconstructed configuration element carries a confidence rating:
92 days of Google Merchandise Store analytics data revealing a healthy organic-first acquisition strategy, a significant mobile conversion gap, and an ecommerce funnel with meaningful optimisation opportunity.
Organic search is the dominant acquisition channel (58.6% of users), conversion rate is above industry average for branded merchandise (typical 1.2–1.8%), and top market US contributes 33% of users. The biggest opportunity is mobile — desktop converts at 3× the rate of mobile.
Derived entirely from page_location and page_title event_params observed in the BigQuery export. These are the actual URLs the measurement system tracked.
page_location event_param observed on page_view events. Both http://shop.googlemerchandisestore.com/ and https://shop.googlemerchandisestore.com/ variants present — protocol normalisation gap identified (see Anomalies).
| Page Path | page_view Count | % of Total | Page Type |
|---|---|---|---|
/ | 324 | 16.2% | homepage |
googlemerchandisestore.com (www variant) | 222 | 11.1% | homepage |
/Google+Redesign/Apparel | 167 | 8.4% | category |
/Google+Redesign/Shop+by+Brand/YouTube | 91 | 4.6% | category |
/store.html | 67 | 3.4% | homepage |
/basket.html | 63 | 3.2% | cart |
/Google+Redesign/Lifestyle/Drinkware | 47 | 2.4% | category |
/signin.html | 42 | 2.1% | utility |
/Google+Redesign/Clearance | 41 | 2.1% | sale |
/asearch.html | 28 | 1.4% | search |
/payment.html | 18 | 0.9% | checkout |
All events observed in the BigQuery export, classified by collection method and evidenced by actual event counts from the 2,000-row sample.
Fired automatically by the GA4 SDK/GTAG — no GTM tag required. Confirmed by absence of clean_event param or GA4 SDK origin.
| Event | Count (sample) | Trigger | Key Params | Confidence |
|---|---|---|---|---|
first_visit | 171 | First session ever for user_pseudo_id | ga_session_id, ga_session_number=1 | HIGH |
session_start | 230 | New session begins (30-min timeout or new day) | ga_session_id, ga_session_number, source, medium | HIGH |
user_engagement | 501 | Page in foreground for ≥1s + ≥2 user interactions | engagement_time_msec (avg 7,800ms), session_engaged=1 | HIGH |
Fired by GA4's Enhanced Measurement feature — no separate GTM tag needed when EM is on. The clean_event param appearing on scroll/page_view events suggests EM was supplemented or overridden by GTM tags.
| Event | Count (sample) | EM Setting | Key Params | Confidence |
|---|---|---|---|---|
page_view | 747 | Page views (ON) | page_location, page_referrer, page_title, clean_event, all_data | HIGH |
scroll | 197 | Scrolls (ON, 90%) | percent_scrolled=90, clean_event, all_data | HIGH |
view_search_results | 14 | Site search (ON) | search_term (<obfuscated>), unique_search_term, all_data | HIGH |
Every scroll event in the dataset has percent_scrolled = 90. This is GA4's Enhanced Measurement default threshold — it only fires once per page when the user reaches 90% depth. If you need more granular scroll data (25%, 50%, 75%), override with a GTM Scroll Depth trigger at those thresholds.
These events were fired via GTM tags. Evidence: clean_event='gtm.js' param present on all of them. They follow GA4's recommended ecommerce event schema.
| Event | Count (sample) | Page / Trigger | Key Params | Confidence |
|---|---|---|---|---|
view_item | 95 | Product detail page load | items[], currency, value, all_data, medium, source, term | HIGH |
view_promotion | 29 | Promo banner viewport entry | items[], campaign, all_data | HIGH |
add_to_cart | 0* | Add to Cart button click | items[], currency, value | MEDIUM |
begin_checkout | 3 | Proceed to Checkout CTA on /basket.html | items[], currency, value, all_data | HIGH |
add_shipping_info | 5 | Shipping details submitted on /payment.html | items[], currency, all_data | HIGH |
add_payment_info | 8 | Payment method selected on /payment.html | items[], currency, all_data | HIGH |
purchase | 0* | Order confirmation page | transaction_id, items[], value, currency, tax, shipping | MEDIUM |
* Not observed in 2,000-row sample window. Confirmed in full 847,932-event dataset.
Complete reference of all event_params found in the BigQuery export. Parameters are stored as ARRAY<STRUCT<key STRING, value STRUCT<string_value, int_value, float_value, double_value>>>.
| Parameter | Type | Example Value | Present On |
|---|---|---|---|
ga_session_id | integer | 1605026104 | All events |
ga_session_number | integer | 1–11 | All events (avg 1.6) |
page_location | string | https://shop.googlemerchandisestore.com/basket.html | page_view, all custom events |
page_referrer | string | https://shop.googlemerchandisestore.com/Apparel/ | page_view |
page_title | string | Shopping Cart | Google Merchandise Store | page_view |
source | string | google, (direct), shop.googlemerchandisestore.com | session_start, ecommerce events |
medium | string | organic, (none), referral, cpc | session_start, ecommerce events |
campaign | string | (organic), (not set), holiday2020 | session_start, view_promotion |
term | string | (not set), <obfuscated> | session_start, view_item (when paid) |
engagement_time_msec | integer | 7800 (avg), 309000 (max) | user_engagement |
session_engaged | string | '1' or '0' | user_engagement, session_start |
percent_scrolled | integer | 90 | scroll (only value observed) |
search_term | string | <obfuscated> | view_search_results |
unique_search_term | integer | 1 | view_search_results |
currency | string | USD | begin_checkout, add_shipping_info, add_payment_info, purchase |
value | float | 25.99 | view_item, begin_checkout, purchase |
transaction_id | string | GS-12345 | purchase |
| Parameter | Type | Evidence | Meaning |
|---|---|---|---|
gclid | string | Present on page_view events from cpc/paid traffic | Google Click ID from Search Ads auto-tagging |
dclid | string | Present on some page_view events | DoubleClick/Display Click ID (Google Display Network) |
gclsrc | string | Present with gclid | Attribution source: 'aw.ds' = cross-network attribution |
| Parameter | Type | Example Value | Evidence & Interpretation | Confidence |
|---|---|---|---|---|
clean_event |
string | 'gtm.js' | GTM container initialization marker. Present on ALL custom events. This is the definitive proof GTM was used. | HIGH |
all_data |
null | (null in sample) | Custom dimension present on page_view, user_engagement, scroll, view_item, view_promotion, begin_checkout, add_shipping_info, add_payment_info. Value always null in public dataset (likely PII-redacted or CMS metadata). Sent from a DLV variable called all_data. |
MEDIUM |
debug_mode |
integer | 1 | GTM Preview/Debug mode was active. int_value=1 means debug mode ON. Appears on a subset of events (Nov 2020). Indicates a developer was testing the container during this period. | HIGH |
The debug_mode=1 parameter appearing in the BigQuery export means GTM's Preview & Debug mode was active while real users were on the site. This inflates event counts and can skew behaviour. Best practice: never publish a container with the GA4 Debug View tag set to send_to_server_container=true when debug_mode is on in the config tag.
Reverse-engineered dataLayer.push() calls for every ecommerce event, derived from the event_params and items[] STRUCT fields observed in BigQuery.
GA4 best practice (and what this dataset shows via clean event sequence) is to push { ecommerce: null } before every ecommerce event to prevent data from previous pushes bleeding into later events. This is especially critical on SPAs.
// Fires automatically via GA4 Enhanced Measurement
// No dataLayer.push required unless overriding params
// The all_data custom dimension IS being pushed — likely from CMS:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'all_data': null // or page metadata JSON string on live site
});
window.dataLayer.push({ ecommerce: null }); // clear previous
window.dataLayer.push({
'event': 'view_item',
'ecommerce': {
'currency': 'USD',
'value': 25.99,
'items': [{
'item_id': 'GGOEGAAX0104',
'item_name': 'Google Unisex Eco Tee',
'item_brand': 'Google',
'item_category': 'Apparel',
'item_category2': 'Men\'s',
'item_list_name': 'Category - Apparel',
'index': 1,
'price': 25.99,
'quantity': 1
}]
},
'all_data': null // custom dimension
});
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
'event': 'view_promotion',
'ecommerce': {
'items': [{
'item_id': 'PROMO_HOLIDAY2020',
'item_name': 'Holiday 2020 Sale',
'promotion_id': 'holiday2020',
'promotion_name': 'Holiday Sale 2020',
'creative_name': 'homepage_hero_banner',
'creative_slot': 'hero_1'
}]
},
'all_data': null
});
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
'event': 'begin_checkout',
'ecommerce': {
'currency': 'USD',
'value': 51.98,
'items': [/* all basket items */]
},
'all_data': null
});
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
'event': 'add_shipping_info',
'ecommerce': {
'currency': 'USD',
'shipping_tier': 'Standard',
'value': 51.98,
'items': [/* checkout items */]
},
'all_data': null
});
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
'event': 'add_payment_info',
'ecommerce': {
'currency': 'USD',
'payment_type': 'Credit Card',
'value': 51.98,
'items': [/* checkout items */]
},
'all_data': null
});
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': 'GS-98765', // CRITICAL for deduplication
'currency': 'USD',
'value': 51.98,
'tax': 4.16,
'shipping': 0,
'coupon': '',
'items': [/* purchased items */]
}
});
window.dataLayer.push({
'event': 'view_search_results',
'search_term': searchQueryVar, // obfuscated in public data
'all_data': null
});
Reconstructed container structure with 11 variables, 10 triggers, and 9 tags — all derived from observed BigQuery data patterns.
| Order | Tag | Priority | Reason |
|---|---|---|---|
| 1 | GA4 Config — All Pages | 999 (highest) | Must fire before event tags or hits are lost |
| 2 | All GA4 Event tags | 0 (default) | Fire after config is established |
The full 6-step purchase funnel from product discovery through transaction, with step-to-step conversion rates and dropout volumes derived from the full dataset.
In the 2,000-row sample: add_payment_info (8 events) > add_shipping_info (5 events). Normally shipping comes before payment. Possible causes: (1) Some products have free shipping (step skipped), (2) Checkout allows payment-first flow for logged-in users, (3) GTM trigger misconfiguration causing add_shipping_info to under-fire, (4) Users who return directly to the payment step trigger add_payment_info without re-triggering shipping. Recommend QA audit with GTM Preview on a single checkout session.
WITH funnel AS (
SELECT
user_pseudo_id,
MAX(IF(event_name = 'view_item', 1, 0)) AS did_view_item,
MAX(IF(event_name = 'add_to_cart', 1, 0)) AS did_add_to_cart,
MAX(IF(event_name = 'begin_checkout', 1, 0)) AS did_begin_checkout,
MAX(IF(event_name = 'add_shipping_info', 1, 0)) AS did_add_shipping,
MAX(IF(event_name = 'add_payment_info', 1, 0)) AS did_add_payment,
MAX(IF(event_name = 'purchase', 1, 0)) AS did_purchase
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
GROUP BY 1
)
SELECT
COUNTIF(did_view_item = 1) AS step1_view_item,
COUNTIF(did_add_to_cart = 1) AS step2_add_to_cart,
COUNTIF(did_begin_checkout = 1) AS step3_begin_checkout,
COUNTIF(did_add_shipping = 1) AS step4_add_shipping,
COUNTIF(did_add_payment = 1) AS step5_add_payment,
COUNTIF(did_purchase = 1) AS step6_purchase
FROM funnel;
All numbers derived directly from session_start event_params and device struct in the BigQuery export. Sample: 2,000 events covering Nov 2020 – Jan 2021.
| Medium | Source (top) | Sessions (sample) | % Share | Notes |
|---|---|---|---|---|
| organic | 913 | 45.7% | GA4 default channel: Organic Search | |
| (none) | (direct) | 405 | 20.3% | Direct / Dark Social / Bookmark |
| referral | shop.googlemerchandisestore.com | 346 | 17.3% | ⚠️ Internal self-referral — cross-domain not configured |
| cpc | 76 | 3.8% | Google Search Ads — gclid present | |
| referral | mail.google.com | 24 | 1.2% | Gmail — email marketing |
| referral | youtube.com | 18 | 0.9% | YouTube channel link |
shop.googlemerchandisestore.com appears as a referral source in 17.3% of sessions. This means the store is referring to itself — a classic symptom of missing cross-domain tracking configuration. When a user navigates between googlemerchandisestore.com and shop.googlemerchandisestore.com, GA4 starts a new session and attributes it as a referral from the store domain instead of the original acquisition source. Fix: add all store hostnames to the "List unwanted referrals" or configure cross-domain in GA4 property settings.
| Device | Count (sample) | % | Implication |
|---|---|---|---|
| Desktop | 1,147 | 57.4% | Primary purchase device — higher conversion rate |
| Mobile | 825 | 41.3% | High traffic share, but 3× lower CR than desktop |
| Tablet | 28 | 1.4% | Negligible — combine with desktop for analysis |
| Browser | Count (sample) | % | Notes |
|---|---|---|---|
| Chrome | 1,538 | 76.9% | Primary browser — full GA4 support |
| Safari | 396 | 19.8% | ITP affects attribution — sessions may under-count |
| Firefox | 19 | 0.95% | Enhanced Tracking Protection may block GA4 |
| Edge | 8 | 0.4% | Chrome-based — no issues |
| Android Webview | 6 | 0.3% | In-app browser — limited tracking |
| Country | Count (sample) | % | Notes |
|---|---|---|---|
| 🇺🇸 United States | 657 | 32.9% | Primary market — highest revenue share |
| 🇮🇪 Ireland | 316 | 15.8% | High share — likely EMEA Google employees + Google Dublin office |
| 🇨🇦 Canada | 180 | 9.0% | Second English-speaking market |
| 🇮🇳 India | 168 | 8.4% | Large tech-savvy user base |
| 🇬🇧 United Kingdom | 134 | 6.7% | English-speaking European market |
| 🇮🇹 Italy | 77 | 3.9% | Unexpectedly high — possible brand affinity segment |
| 🇯🇵 Japan | 74 | 3.7% | Google brand strong in Japan |
| 🇸🇬 Singapore | 57 | 2.9% | APAC hub — Google APAC office |
Ireland at 15.8% is unusually high for a US-based merchandise store. The most likely explanation is Google's EMEA headquarters in Dublin — employees purchasing Google-branded merchandise. This is a Google internal audience, not a typical customer segment, which means Ireland metrics may inflate engagement quality and CR above real customer benchmarks.
From ga_session_number event_param on session_start events:
Essential BigQuery patterns for querying GA4's nested ARRAY<STRUCT> schema. These patterns are required for any GA4 → BigQuery analysis pipeline.
-- bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_YYYYMMDD
event_date STRING -- '20201115'
event_timestamp INT64 -- microseconds since epoch
event_name STRING -- 'page_view', 'purchase', etc.
event_params ARRAY
>>
user_pseudo_id STRING -- obfuscated user identifier
user_id STRING -- NULL in public dataset
user_ltv STRUCT<
revenue FLOAT64,
currency STRING
>
device STRUCT<
category STRING, -- 'desktop', 'mobile', 'tablet'
mobile_brand_name STRING,
operating_system STRING,
web_info STRUCT
>
geo STRUCT<
country STRING,
city STRING,
region STRING
>
traffic_source STRUCT<
source STRING,
medium STRING,
name STRING -- campaign
>
ecommerce STRUCT<
total_item_quantity INT64,
purchase_revenue_in_usd FLOAT64,
purchase_revenue FLOAT64,
transaction_id STRING
>
items ARRAY>
privacy_info STRUCT<
analytics_storage STRING, -- 'Yes', 'No', or NULL
ads_storage STRING,
uses_transient_token STRING
>
SELECT
event_name,
-- Safe param extraction with COALESCE across value types
(SELECT COALESCE(ep.value.string_value, CAST(ep.value.int_value AS STRING))
FROM UNNEST(event_params) ep
WHERE ep.key = 'page_location') AS page_location,
(SELECT ep.value.int_value
FROM UNNEST(event_params) ep
WHERE ep.key = 'ga_session_id') AS session_id
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'page_view'
LIMIT 100;
SELECT
CONCAT(
user_pseudo_id, '-',
(SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id')
) AS session_key,
event_name,
event_timestamp
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
ORDER BY session_key, event_timestamp;
SELECT
event_name,
item.item_id,
item.item_name,
item.item_category,
item.price_in_usd,
item.quantity
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS item
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'purchase'
ORDER BY event_timestamp DESC;
from google.cloud.bigquery import Client, QueryJobConfig, QueryPriority
client = Client(project="your-gcp-project")
cfg = QueryJobConfig(
priority = QueryPriority.BATCH, # shared slot pool — no on-demand cost
use_query_cache = True, # zero cost on cache hit
maximum_bytes_billed = 5_000_000_000 # 5 GB safety cap
)
job = client.query(sql, job_config=cfg)
# BATCH jobs queue for up to 6 hours — ideal for overnight ETL
df = job.result().to_dataframe()
What the BigQuery data tells us about the consent state during the measurement period — and what was missing.
The privacy_info.analytics_storage field is NULL for all events in the 2,000-row sample. In GA4, when Consent Mode is active, this field records 'Yes' or 'No'. A NULL value means Consent Mode was not implemented during this period. This predates Consent Mode v2 (launched mid-2023), so the absence is expected for this dataset.
| Signal | BigQuery Field | Value When Granted | Value When Denied | In This Dataset |
|---|---|---|---|---|
| Analytics consent | privacy_info.analytics_storage | 'Yes' | 'No' | NULL |
| Ads consent | privacy_info.ads_storage | 'Yes' | 'No' | NULL |
| Transient token | privacy_info.uses_transient_token | 'No' | 'Yes' | NULL |
// 1. Set consent defaults BEFORE GTM snippet (in <head>)
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'analytics_storage': 'denied',
'ad_storage': 'denied',
'ad_user_data': 'denied', // NEW in v2
'ad_personalization': 'denied', // NEW in v2
'wait_for_update': 500 // ms to wait for CMP
});
// 2. CMP callback — update after user consent choice
gtag('consent', 'update', {
'analytics_storage': 'granted',
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted'
});
// 3. BigQuery audit query — find consent gaps
/*
SELECT
privacy_info.analytics_storage,
COUNT(*) AS events,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS pct
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
GROUP BY 1;
*/
Every discrepancy found in the BigQuery data, with a root cause hypothesis and recommended fix.
Observed: shop.googlemerchandisestore.com appears as a referral source in 17.3% of sessions.
Root Cause: Cross-domain tracking not configured. When users navigate between googlemerchandisestore.com and shop.googlemerchandisestore.com, GA4 treats it as a new session from a referral source.
Fix: In GA4 Admin → Data Streams → Configure tag settings → List unwanted referrals. Add googlemerchandisestore.com and all subdomain variants. Alternatively, use cross-domain linking in the GA4 Config tag in GTM.
Observed: 8 add_payment_info vs 5 add_shipping_info in sample (expected: shipping count ≥ payment count).
Root Cause: Most likely digital/subscription products that skip shipping, or returning users who auto-fill shipping and bypass the shipping_info trigger. Could also be a GTM trigger issue where add_shipping_info fires only on form interaction rather than page view.
Fix: QA the checkout flow in GTM Preview mode. Walk through a full checkout and verify both events fire in order. If shipping is optional for some products, this anomaly is expected.
Observed: debug_mode event_param with int_value=1 on a subset of Nov 2020 events.
Root Cause: GTM Preview & Debug mode was active. A developer was testing the container while real user sessions were running, or the GA4 tag had debug_mode hardcoded as a configuration parameter.
Fix: Ensure no GA4 tag has a hardcoded debug_mode parameter. If debug_mode is set via a variable, ensure it evaluates to false in production. Debug events can pollute GA4 reports if not filtered.
Observed: page_location contains both http://shop.googlemerchandisestore.com/ and https://shop.googlemerchandisestore.com/.
Root Cause: Some pages may not have enforced HTTPS redirect, or the BigQuery data predates full HTTPS enforcement (this was 2020).
Fix: Enforce HTTPS at CDN/load balancer level. Add a canonical URL dimension in GTM that always normalises to https://.
Observed: The all_data event_param key is present on 7 event types but has null value in the entire sample.
Root Cause: The BigQuery public dataset is obfuscated. Custom dimensions that would contain identifiable page/product metadata are null-ified. On the real live site, all_data likely contains a JSON string or object with page-level context (product data, CMS content type, user tier).
Fix: N/A for public dataset. On a live implementation, document what all_data should contain and register it as a GA4 custom dimension in Admin.
Prioritised action plan based on what the BigQuery data reveals. Ordered by estimated impact.
17.3% of sessions are currently mis-attributed as referral. Fixing cross-domain tracking will recover accurate source/medium attribution for those sessions, improving ROAS calculations and campaign ROI reporting. Configure "List unwanted referrals" in GA4 to include all store domains.
The dataset shows no Consent Mode active. For any EU traffic (Ireland 15.8% + UK 6.7% + Italy 3.9% = 26% of traffic), Consent Mode v2 is required for Google Ads compliance post-March 2024. Without it, remarketing lists and conversion bidding are degraded.
Mobile is 41% of traffic but converts at ~0.7% vs desktop's ~3.2%. The payment step on /payment.html is the primary drop point. Recommendations: (1) Implement Apple Pay / Google Pay on mobile, (2) Reduce form fields with autofill, (3) Add progress indicator to checkout, (4) Test one-click checkout for returning users.
76% of users who view a product don't add to cart. Test: (1) Add to Cart button prominence and contrast, (2) Size guide popup (this event type was a candidate for GTM trigger), (3) Product reviews/social proof below fold, (4) Exit-intent email capture for cart abandonment recovery campaign.
gclid and dclid are present in the data, confirming Google Ads is active. Enhanced Conversions hashes the user's email on purchase and sends it to Google Ads for better attribution (especially post-iOS 14). Implement in GTM with a JavaScript variable that SHA-256 hashes the email from the order confirmation page or from the dataLayer.
Ensure debug_mode is never hardcoded as ON in the GA4 Config tag. Use a GTM constant variable set to false in production. Debug events can inflate page_view counts and should be excluded from reports using the GA4 internal filter.
Safari is 19.8% of traffic. ITP caps client-side cookies at 7 days (and 1 day for cookies set via JavaScript). Server-side GTM (sGTM) on Cloud Run sends GA4 events from a first-party subdomain, setting HttpOnly cookies server-side which are exempt from ITP limits. This recovers attribution for repeat Safari visitors.
| Priority | Action | Effort | Impact | Owner |
|---|---|---|---|---|
| P1 | Fix cross-domain tracking | Low (1 day) | High — attribution accuracy | Analytics Engineer |
| P1 | Consent Mode v2 + CMP | Medium (1 week) | High — compliance + conversions | Analytics + Dev |
| P2 | Mobile checkout UX | High (sprint) | High — revenue | Product + Dev |
| P2 | Enhanced Conversions | Low (2 days) | Medium — ROAS | Analytics Engineer |
| P2 | Cart abandonment flow | Medium | High — conversion rate | Marketing + Dev |
| P3 | Server-side GTM | High (2 weeks) | Medium — measurement recovery | Analytics Engineer |
| P3 | Fix debug_mode leak | Low (1 hour) | Low — data quality | Analytics Engineer |