Google Analytics 4 (GA4)
Google Analytics 4 (GA4) is configured within the unified gtag destination. walkerOS loads the gtag script and forwards mapped events to GA4.
Installation
npm install @walkeros/web-destination-gtagimport { startFlow } from '@walkeros/collector';
import { destinationGtag } from '@walkeros/web-destination-gtag';
await startFlow({
destinations: {
ga4: {
code: destinationGtag,
config: {
settings: {
ga4: {
measurementId: 'G-XXXXXXXXXX',
},
},
},
},
},
});Serve gtag.js first-party
By default gtag.js loads from googletagmanager.com. To serve it from your own
tagging server instead, set scriptSrc to the
tag serving path
configured in your server container. The path maps to the measurement ID inside
the container, so the ID is not part of the URL.
settings: {
ga4: {
measurementId: 'G-XXXXXXXXXX',
scriptSrc: 'https://example.com/metrics/tag_serving_path/',
server_container_url: 'https://example.com/metrics',
},
}server_container_url routes measurement data and must not include the tag
serving path. Setting it alone does not change where the script loads from,
because serving gtag.js is a separate capability your container may not have. If
scriptSrc fails to load, walkerOS falls back to googletagmanager.com so a
misconfigured container degrades to working measurement rather than none.
A page view needs no mapping
The gtag destination snake-cases an entity action event name into a GA4 event
name automatically, so page view arrives in GA4 as page_view with no mapping
at all. An unmapped product add would arrive as product_add carrying only
send_to, which is why the ecommerce event below needs an explicit mapping.
Add the ecommerce mapping
Nest the mapping under the gtag destination to turn a product add event into
the GA4 add_to_cart event. The product price becomes the GA4 value, the
currency falls back to EUR, and the product is shaped into a GA4 items array
via a this loop.
mapping: {
product: {
add: {
name: 'add_to_cart',
data: {
map: {
value: 'data.price',
currency: { value: 'EUR', key: 'data.currency' },
items: {
loop: [
'this',
{
map: {
item_id: 'data.id',
item_name: 'data.name',
quantity: { value: 1, key: 'data.quantity' },
},
},
],
},
},
},
},
},
}Pushing a product add event:
elb('product add', { id: 'ers', name: 'Everyday Ruck Snack', price: 420 });produces the GA4 add_to_cart event:
gtag('event', 'add_to_cart', {
value: 420,
currency: 'EUR',
items: [{ item_id: 'ers', item_name: 'Everyday Ruck Snack', quantity: 1 }],
send_to: 'G-XXXXXXXXXX',
});Verify in GA4
Copy your Measurement ID from GA4 > Admin > Data Streams and open your web
stream. Watch Reports > Realtime to confirm the page_view event, then open
Admin > DebugView to inspect the add_to_cart event and its parameters as they
arrive.
Configuration
This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.
Settings
| Property | Type | Description | More |
|---|---|---|---|
como | boolean | object | Consent mode configuration: false (disabled), true (use defaults), or custom mapping | |
como_advanced | boolean | number | Advanced consent mode (non-EU): emit denied default at page load with wait_for_update (true=500ms, or a number of ms) | |
ga4 | ga4 | GA4-specific configuration settings | |
measurementId | string | GA4 Measurement ID from Google Analytics | |
debug | boolean | Enable debug mode for GA4 | |
pageview | boolean | Enable automatic pageview tracking | |
server_container_url | string | Server-side GTM container URL | |
snakeCase | boolean | Convert parameter names to snake_case | |
transport_url | string | Custom transport URL for GA4 | |
data | any | Custom data mapping configuration | |
ads | ads | Google Ads specific configuration settings | |
conversionId | string | Google Ads Conversion ID (required) | |
currency | string | Currency code (ISO 4217, e.g., USD, EUR) | |
data | any | Custom data mapping (WalkerOS.Mapping.Value | Values) | |
enhancedConversions | enhancedConversions | Enhanced conversions: maps event fields to Google user_data for improved measurement accuracy | |
email | any | Mapping value for user email | |
phone_number | any | Mapping value for user phone number | |
address | address | Address mapping for enhanced conversions | |
first_name | any | Mapping value for first name | |
last_name | any | Mapping value for last name | |
street | any | Mapping value for street address | |
city | any | Mapping value for city | |
region | any | Mapping value for region/state | |
postal_code | any | Mapping value for postal/zip code | |
country | any | Mapping value for country | |
gtm | gtm | Google Tag Manager specific configuration settings | |
containerId | string | GTM Container ID (required) | |
dataLayer | string | Custom dataLayer variable name (default: dataLayer) | |
domain | string | Custom GTM domain for script loading | |
data | any | Custom data mapping (WalkerOS.Mapping.Value | Values) |
Mapping
Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
| Property | Type | Description | More |
|---|---|---|---|
ga4 | ga4 | GA4-specific event mapping | |
ads | ads | Google Ads-specific event mapping | |
label | string | Conversion label for this specific event | |
gtm | gtm | GTM-specific event mapping |
Examples
Add to cart
A product add event is mapped to the GA4 add_to_cart event with item details and value.
{
"name": "product add",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"intent",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "ee28e37c00fdc812",
"trigger": "click",
"entity": "product",
"action": "add",
"timestamp": 1700000101,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "add_to_cart",
"include": [
"data"
],
"data": {
"map": {
"currency": {
"value": "EUR",
"key": "data.currency"
},
"value": "data.price",
"items": {
"loop": [
"this",
{
"map": {
"item_id": "data.id",
"item_variant": "data.color",
"quantity": {
"value": 1,
"key": "data.quantity"
}
}
}
]
}
}
}
}gtag("event", "add_to_cart", {
"currency": "EUR",
"value": 420,
"items": [
{
"item_id": "ers",
"item_variant": "black",
"quantity": 1
}
],
"data_id": "ers",
"data_name": "Everyday Ruck Snack",
"data_color": "black",
"data_size": "l",
"data_price": 420,
"send_to": "G-XXXXXX-1"
})Enhanced conversions
enhancedConversions maps event fields into a gtag set user_data call sent immediately before the conversion event.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555,
"customerEmail": "buyer@shop.com",
"customerPhone": "+1234567890"
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "dc2aff4654564d61",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000109,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "PURCHASE_EC2",
"settings": {
"ads": {
"label": "PURCHASE_EC2"
}
},
"data": {
"map": {
"value": "data.total"
}
}
}gtag("set", "user_data", {
"email": "buyer@shop.com",
"phone_number": "+1234567890"
});
gtag("event", "conversion", {
"send_to": "AW-123456789/PURCHASE_EC2",
"currency": "EUR",
"value": 555
})Google Ads init
The destination bootstraps gtag and configures a Google Ads account via its conversionId.
{
"settings": {
"ads": {
"conversionId": "AW-123456789",
"currency": "EUR"
}
}
}gtag("js", {});
gtag("config", "AW-123456789")Consent Mode v2
A walker consent command with marketing and functional granted updates gtag Consent Mode v2 parameters.
{
"marketing": true,
"functional": true
}gtag("consent", "default", {
"ad_storage": "denied",
"ad_user_data": "denied",
"ad_personalization": "denied",
"analytics_storage": "denied"
});
gtag("consent", "update", {
"ad_storage": "granted",
"ad_user_data": "granted",
"ad_personalization": "granted",
"analytics_storage": "granted"
})GA4 init
The destination bootstraps gtag and configures a GA4 property via its measurementId.
{
"settings": {
"ga4": {
"measurementId": "G-XXXXXX-1"
}
}
}gtag("js", {});
gtag("config", "G-XXXXXX-1", {})GA4 include all
Include flattens every event section into prefixed GA4 params, exposing data, context, user, source, and event fields.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "ev-1700000106",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000106,
"timing": 3.14,
"source": {
"count": 1,
"trace": "1700000106abcdef1700000106abcdef",
"type": "collector",
"schema": "4"
}
}{
"include": [
"data",
"context",
"globals",
"user",
"source",
"event"
]
}gtag("event", "page_view", {
"data_domain": "www.example.com",
"data_title": "walkerOS documentation",
"data_referrer": "https://www.walkeros.io/",
"data_search": "?foo=bar",
"data_hash": "#hash",
"data_id": "/docs/",
"context_dev": "test",
"globals_pagegroup": "docs",
"user_id": "us3r",
"user_device": "c00k13",
"user_session": "s3ss10n",
"source_type": "collector",
"source_schema": "4",
"source_count": 1,
"source_trace": "1700000106abcdef1700000106abcdef",
"source_release_default": "4.3.2",
"event_entity": "page",
"event_action": "view",
"event_trigger": "load",
"event_id": "ev-1700000106",
"event_name": "page view",
"event_timestamp": 1700000106,
"event_timing": 3.14,
"send_to": "G-XXXXXX-1"
})Ads conversion
An order complete event is sent as a Google Ads conversion with a configured label and transaction value.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "dc11510eda496e31",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000103,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "PURCHASE_CONV",
"settings": {
"ads": {
"label": "PURCHASE_CONV"
}
},
"data": {
"map": {
"value": "data.total"
}
}
}gtag("event", "conversion", {
"send_to": "AW-123456789/PURCHASE_CONV",
"currency": "EUR",
"value": 555
})GTM dataLayer push
A page view event is pushed to window.dataLayer for GTM with the mapped event name and parameters.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "fdadd8c58a6131e8",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000104,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "page_view",
"settings": {
"gtm": {}
},
"data": {
"map": {
"page_title": "data.title",
"page_location": "data.domain"
}
}
}dataLayer.push({
"event": "page_view",
"page_title": "walkerOS documentation",
"page_location": "www.example.com"
})GTM init
The destination initializes the dataLayer and pushes the gtm.js start event for a GTM container.
{
"settings": {
"gtm": {
"containerId": "GTM-XXXXXXX"
}
}
}dataLayer.push({
"gtm.start": 1700000000000,
"event": "gtm.js"
})Multi-tool push
A single order event fans out to GA4, Google Ads, and GTM from one mapping rule with per-tool settings.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "ee72bc16793de2e5",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000107,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "purchase",
"include": [
"data"
],
"settings": {
"ga4": {},
"ads": {
"label": "PURCHASE_CONV"
},
"gtm": {}
},
"data": {
"map": {
"value": "data.total",
"currency": {
"key": "data.currency",
"value": "EUR"
}
}
}
}gtag("event", "purchase", {
"value": 555,
"currency": "EUR",
"data_id": "0rd3r1d",
"data_currency": "EUR",
"data_shipping": 5.22,
"data_taxes": 73.76,
"data_total": 555,
"send_to": "G-XXXXXX-1"
});
gtag("event", "conversion", {
"send_to": "AW-123456789/PURCHASE_CONV",
"currency": "EUR",
"value": 555,
"data_id": "0rd3r1d",
"data_currency": "EUR",
"data_shipping": 5.22,
"data_taxes": 73.76,
"data_total": 555
});
dataLayer.push({
"event": "purchase",
"value": 555,
"currency": "EUR",
"data_id": "0rd3r1d",
"data_currency": "EUR",
"data_shipping": 5.22,
"data_taxes": 73.76,
"data_total": 555
})Page view
A page view event is forwarded as a GA4 page_view event with no additional mapping.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "0f251ef6d8d35b51",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000102,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}gtag("event", "page_view", {
"send_to": "G-XXXXXX-1"
})Purchase
An order complete event is mapped to the GA4 purchase event with transaction details and nested product items.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "8db695cb04ac3a7e",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000100,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "purchase",
"include": [
"data",
"context"
],
"data": {
"map": {
"transaction_id": "data.id",
"value": "data.total",
"tax": "data.taxes",
"shipping": "data.shipping",
"currency": {
"key": "data.currency",
"value": "EUR"
},
"items": {
"loop": [
"nested",
{
"condition": {
"$code": "e=>U(e)&&\"product\"===e.entity"
},
"map": {
"item_id": "data.id",
"item_name": "data.name",
"quantity": {
"key": "data.quantity",
"value": 1
}
}
}
]
}
}
}
}gtag("event", "purchase", {
"transaction_id": "0rd3r1d",
"value": 555,
"tax": 73.76,
"shipping": 5.22,
"currency": "EUR",
"items": [
{
"item_id": "ers",
"item_name": "Everyday Ruck Snack",
"quantity": 1
},
{
"item_id": "cc",
"item_name": "Cool Cap",
"quantity": 1
}
],
"data_id": "0rd3r1d",
"data_currency": "EUR",
"data_shipping": 5.22,
"data_taxes": 73.76,
"data_total": 555,
"context_shopping": "complete",
"send_to": "G-XXXXXX-1"
})