GA4
Decoder transformer that turns Google Analytics 4 Measurement Protocol v2 hits (/g/collect, /mp/collect) into walkerOS events. Drop it in a server source's before chain to ingest existing gtag/Google Tag traffic without changing the front-end. One HTTP request can carry many GA4 events; the transformer returns one walkerOS event per GA4 event in the hit.
This is the v1 release (0.1.0) with an explicit scope: server-side decoding via source-express, GA4 v2 only, replace-not-merge mapping semantics. See Caveats and Roadmap for the boundaries.
Installation
Wire it up
The transformer reads ctx.ingest.url (required) and ctx.ingest.body (optional) from the source it sits in front of. The recommended pairing is @walkeros/server-source-express:
- Bundled
- Integrated
{
"version": 4,
"flows": {
"default": {
"config": { "platform": "server" },
"sources": {
"http": {
"package": "@walkeros/server-source-express",
"config": {
"ingest": {
"url": "req.url",
"body": "req.body"
}
},
"before": "ga4"
}
},
"transformers": {
"ga4": { "package": "@walkeros/transformer-ga4" }
},
"destinations": {
"log": { "package": "@walkeros/destination-demo" }
}
}
}
}
import { startFlow } from '@walkeros/collector';
import { sourceExpress } from '@walkeros/server-source-express';
import { transformerGa4 } from '@walkeros/transformer-ga4';
import { destinationDemo } from '@walkeros/destination-demo';
await startFlow({
sources: {
http: {
code: sourceExpress,
config: {
ingest: { url: 'req.url', body: 'req.body' },
},
before: 'ga4',
},
},
transformers: {
ga4: { code: transformerGa4 },
},
destinations: {
log: { code: destinationDemo },
},
});
Ingest contract
The transformer expects the source to populate ctx.ingest with these keys:
| Key | Type | Required | Notes |
|---|---|---|---|
url | string | yes | Full request URL including the query string. |
body | string | no | Raw POST body. Multi-event batches are \n-separated lines. |
If url is missing or not a string the transformer drops the event silently. If body is JSON-parsed by the source before reaching the transformer, pass the original raw string through or skip the transformer.
Configuration
This transformer uses the standard transformer config wrapper (consent, data, env, id, ...). For the shared fields see transformer configuration. Package-specific fields live under config.settings and are listed below.
Settings
This package has no package-specific settings.
Mapping
This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
Examples
Add to cart
A GA4 add_to_cart hit decoded to a walkerOS product add event with currency and value.
Batched POST (fan-out)
A single POST request carrying two newline-separated events fans out into two walkerOS events.
Begin checkout
A GA4 begin_checkout hit decoded to a walkerOS order start event with currency, value, and coupon.
Consent denied (gcs=G100)
A page_view hit with gcs=G100 still maps, with consent.{marketing,analytics} both false on the resulting event.
Custom event (* fallback)
Unknown GA4 event names hit the * fallback rule and surface as a ga4 track event carrying the original name.
Login
A GA4 login hit decoded to a walkerOS session login event with the auth method.
Page view
A standard GA4 page_view hit decoded to a walkerOS page view with id, title, and referrer.
Purchase (canary)
A GA4 purchase hit decoded to a walkerOS order complete event with id, currency, total, tax, shipping, and coupon.
Scroll
A GA4 scroll hit decoded to a walkerOS page scroll event with the percent_scrolled value.
Search
A GA4 search hit decoded to a walkerOS search submit event carrying the search term.
user_engagement (ignored)
Auto-fired GA4 user_engagement events are dropped by default — the transformer returns false.
View item
A GA4 view_item hit decoded to a walkerOS product view event with currency and value.
Default mappings
transformer-ga4 ships with default mappings for 33 standard GA4 event names. Out of the box, you get pageviews, ecommerce, list/promotion, engagement, and auth events mapped to walkerOS's entity-action naming.
Page / scroll / click
GA4 (en) | walkerOS (name) | Fields |
|---|---|---|
page_view | page view | id, title, referrer |
scroll | page scroll | percent |
click | link click | url, domain, outbound |
file_download | file download | name, extension, url |
Ecommerce
GA4 (en) | walkerOS (name) | Fields |
|---|---|---|
view_item | product view | currency, value |
add_to_cart | product add | currency, value |
remove_from_cart | product remove | currency, value |
view_cart | cart view | currency, value |
begin_checkout | order start | currency, value, coupon |
add_shipping_info | order shipping | currency, value, tier |
add_payment_info | order payment | currency, value, type |
purchase | order complete | id, currency, total, tax, shipping |
refund | order refund | id, currency, total |
add_to_wishlist | wishlist add | currency, value |
List / promotion
GA4 (en) | walkerOS (name) | Fields |
|---|---|---|
view_item_list | list view | id, name |
select_item | product click | list_id, list_name |
view_promotion | promotion view | reads from items[0] |
select_promotion | promotion click | reads from items[0] |
select_content | content select | type, id |
Video / form / search
GA4 (en) | walkerOS (name) | Fields |
|---|---|---|
video_start | video start | title, duration, current, percent |
video_progress | video progress | same as video_start |
video_complete | video complete | same as video_start |
form_start | form start | id, name, destination |
form_submit | form submit | id, name, destination |
search | search submit | term |
Auth / lead / share
GA4 (en) | walkerOS (name) | Fields |
|---|---|---|
login | session login | method |
sign_up | session signup | method |
generate_lead | lead generate | currency, value |
share | content share | method, type, id |
Auto-fired noise (dropped by default)
GA4 (en) | Behavior |
|---|---|
user_engagement | ignore: true |
session_start | ignore: true |
first_visit | ignore: true |
These events are emitted automatically by gtag and rarely carry analytics intent. Override the rule if you need them.
Fallback
GA4 (en) | walkerOS (name) | Fields |
|---|---|---|
'*' | ga4 track | data.event_name = original en |
Any GA4 event name not listed above falls through to '*' and produces a generic ga4 track walkerOS event. Override '*' to change the fallback rule globally.
Override a default field
User config replaces the matching default rule per event name. Other events keep their defaults. To swap a field on purchase:
{
"transformers": {
"ga4": {
"package": "@walkeros/transformer-ga4",
"config": {
"settings": {
"mapping": {
"purchase": {
"name": "order complete",
"data": {
"map": {
"id": "params.ep.transaction_id",
"total": "params.epn.value",
"currency": "params.ep.currency",
"coupon": "params.ep.promo_code"
}
}
}
}
}
}
}
}
}
Because v1 uses replace semantics, the entire purchase rule is taken from user config: copy any default fields you want to keep. Additive per-field merge is on the roadmap.
Drop an event
Set ignore: true on any key to prevent it from being emitted:
"settings": {
"mapping": {
"click": { "ignore": true }
}
}
This is how user_engagement, session_start, and first_visit are silenced by default.
Custom events
Two patterns:
1. Override '*' to change the global fallback for unknown GA4 event names:
"settings": {
"mapping": {
"*": {
"name": "custom event",
"data": { "map": { "event_name": "name" } }
}
}
}
2. Add a specific key for an event you fire via gtag('event', '<your_name>', ...):
"settings": {
"mapping": {
"newsletter_subscribe": {
"name": "newsletter signup",
"data": { "map": { "source": "params.ep.source" } }
}
}
}
Tracking ID filtering
By default only Measurement IDs starting with G- are accepted; Ads (AW-) and DC (DC-) hits are dropped. Widen via a string regex in settings.tidPattern:
"settings": {
"tidPattern": "^(G|AW|DC)-"
}
The string is compiled to a RegExp at init time.
Caveats
- Replace semantics, not merge. A user mapping rule fully replaces the matching default rule. There is no per-field merge inside
data.mapin v1. - GA4 v2 only. Assumes the v2 Measurement Protocol layout (
ep.,epn.,up.,upn.,prN,gcs). v1 is out of scope. G-tids only by default. OverridetidPatternto capture Ads and DC traffic.- Basic
gcsonly. MapsG1XXtomarketing/analyticsbooleans. Functional/preferences flags and the newergcdparameter are not decoded. - Body must be raw text. The transformer parses POST bodies as URL-encoded form lines. Pre-parsed JSON bodies will not decode.
- Ingest contract is required. Source wiring must populate
ctx.ingest.url(required) andctx.ingest.body(optional) for batched hits.
Roadmap
- Additive per-field merge so partial overrides extend the default rule instead of replacing it
- Web ingest via interception sources for capturing
gtagtraffic from the browser - More vendor decoders (Segment, Snowplow, Adobe) following the same
before-chain pattern - Richer consent decoding (
gcd, functional/preferences flags)
Next steps
- Create your own - Build custom transformers
- Server source: express - Pair the decoder with the HTTP source