Webclat / Martech Practice
Webclat / Martech Practice  /  qa  /  attribution

How do I reconstruct a user's full cross-channel journey from raw GA4 BigQuery export tables?

Answer in brief

Query the events_* (and events_intraday_* for same-day data) tables grouped by user_pseudo_id, ordered by event_timestamp, extracting the traffic-source parameters at each session boundary - the journey is the ordered list of distinct session-level channel touches between a user's first recorded event and the event you are analyzing, typically a conversion.

Why this happens

GA4's BigQuery export is intentionally granular - one row per event, not per session or per user - which is exactly what makes journey reconstruction possible but also means you must do the session- and journey-level aggregation yourself. The UI's own reports pre-aggregate this into fixed views, like the default channel groupings, that do not expose the raw ordered sequence a Markov model or journey-visualization tool needs.

The schema itself has changed across GA4's life - traffic-source attribution fields moved and were renamed as Google refined the export - so a query written against an older schema reference can silently return null or empty traffic-source fields against a newer export. This is a common source of "the journey looks incomplete" that is actually a schema-version mismatch, not a real data gap.

Fix it

  1. Confirm which GA4 BigQuery export schema version you are working with, and use the field names that match - particularly for the traffic-source struct, which changed across GA4's rollout.
  2. Write a query that unnests event_params per row to pull out the session ID, then groups rows into sessions per user_pseudo_id.
  3. For each session, extract the traffic-source values GA4 attaches at session start (source, medium, campaign, or the newer collected_traffic_source struct) as that session's channel touch.
  4. Order each user's sessions by timestamp to produce their journey: an ordered list of channel touches from first recorded activity to the event of interest.
  5. Decide explicitly how to handle direct/none sessions in the middle of a journey - a common convention treats them as a continuation of the prior known channel, or as their own distinct "Direct" touch - and document that choice, since it materially changes downstream attribution results.
  6. Watch for user_pseudo_id churn - cleared cookies, cross-device, incognito - which fragments what is really one person into multiple apparent users in the raw export, a known ceiling of cookie-based reconstruction, not a query bug.

How to verify it worked

Pick one real, recent conversion and manually trace that user's session history in GA4's own user-level exploration to compare against what your BigQuery query reconstructs for the same user_pseudo_id - the touch sequence and channel labels should match. A mismatch usually traces back to a schema-field version issue or a session-boundary definition difference between your query and GA4's own session logic.

Still Seeing This After Trying the Fix?

Send us what you are seeing - the console error, the Network tab, the Live View output. We trace tracking implementations for a living and can usually tell you what is actually happening in one look.

Ask An Engineer