Skip to main content

Google Analytics 4 (GA4)

Web Source code Package

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-gtag
import { 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

PropertyTypeDescriptionMore
comoboolean | objectConsent mode configuration: false (disabled), true (use defaults), or custom mapping
como_advancedboolean | numberAdvanced consent mode (non-EU): emit denied default at page load with wait_for_update (true=500ms, or a number of ms)
ga4ga4GA4-specific configuration settings
measurementId*stringGA4 Measurement ID from Google Analytics
debugbooleanEnable debug mode for GA4
pageviewbooleanEnable automatic pageview tracking
server_container_urlstringServer-side GTM container URL
snakeCasebooleanConvert parameter names to snake_case
transport_urlstringCustom transport URL for GA4
dataanyCustom data mapping configuration
adsadsGoogle Ads specific configuration settings
conversionId*stringGoogle Ads Conversion ID (required)
currencystringCurrency code (ISO 4217, e.g., USD, EUR)
dataanyCustom data mapping (WalkerOS.Mapping.Value | Values)
enhancedConversionsenhancedConversionsEnhanced conversions: maps event fields to Google user_data for improved measurement accuracy
emailanyMapping value for user email
phone_numberanyMapping value for user phone number
addressaddressAddress mapping for enhanced conversions
first_nameanyMapping value for first name
last_nameanyMapping value for last name
streetanyMapping value for street address
cityanyMapping value for city
regionanyMapping value for region/state
postal_codeanyMapping value for postal/zip code
countryanyMapping value for country
gtmgtmGoogle Tag Manager specific configuration settings
containerId*stringGTM Container ID (required)
dataLayerstringCustom dataLayer variable name (default: dataLayer)
domainstringCustom GTM domain for script loading
dataanyCustom data mapping (WalkerOS.Mapping.Value | Values)
* Required fields

Mapping

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
ga4ga4GA4-specific event mapping
adsadsGoogle Ads-specific event mapping
labelstringConversion label for this specific event
gtmgtmGTM-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.

Event
{
  "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"
  }
}
Mapping
{
  "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"
              }
            }
          }
        ]
      }
    }
  }
}
Out
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.

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"
  }
}
Mapping
{
  "name": "PURCHASE_EC2",
  "settings": {
    "ads": {
      "label": "PURCHASE_EC2"
    }
  },
  "data": {
    "map": {
      "value": "data.total"
    }
  }
}
Out
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.

Event
{
  "settings": {
    "ads": {
      "conversionId": "AW-123456789",
      "currency": "EUR"
    }
  }
}
Out
gtag("js", {});

gtag("config", "AW-123456789")

Consent Mode v2

A walker consent command with marketing and functional granted updates gtag Consent Mode v2 parameters.

Event
{
  "marketing": true,
  "functional": true
}
Out
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.

Event
{
  "settings": {
    "ga4": {
      "measurementId": "G-XXXXXX-1"
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "include": [
    "data",
    "context",
    "globals",
    "user",
    "source",
    "event"
  ]
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "name": "PURCHASE_CONV",
  "settings": {
    "ads": {
      "label": "PURCHASE_CONV"
    }
  },
  "data": {
    "map": {
      "value": "data.total"
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "name": "page_view",
  "settings": {
    "gtm": {}
  },
  "data": {
    "map": {
      "page_title": "data.title",
      "page_location": "data.domain"
    }
  }
}
Out
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.

Event
{
  "settings": {
    "gtm": {
      "containerId": "GTM-XXXXXXX"
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "name": "purchase",
  "include": [
    "data"
  ],
  "settings": {
    "ga4": {},
    "ads": {
      "label": "PURCHASE_CONV"
    },
    "gtm": {}
  },
  "data": {
    "map": {
      "value": "data.total",
      "currency": {
        "key": "data.currency",
        "value": "EUR"
      }
    }
  }
}
Out
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.

Event
{
  "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"
  }
}
Out
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.

Event
{
  "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"
  }
}
Mapping
{
  "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
              }
            }
          }
        ]
      }
    }
  }
}
Out
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"
})
💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)