Every GTM professional knows the debug loop: make a change, open Preview mode, test a conversion on your own machine, confirm the tag fires, publish. It works. But it only validates one thing — that the tag fires correctly in a clean browser session on your device, with your network, on the page you thought to test.

Real tracking problems don't usually show up in Preview mode. They show up as a 15% drop in form_submit events on a Tuesday afternoon, or as a purchase event that fires correctly on Chrome but silently fails on a specific iOS version. They show up three hours after a container publish, not during testing. And by the time someone notices, you're staring at a GA4 report trying to diagnose something that happened to real users you can't replay.

BigQuery's GA4 intraday export changes this. It gives you a queryable record of every event sent to GA4 today — across all users, all browsers, all devices — updated continuously throughout the day. You can query it 30 minutes after a GTM deployment to verify it's working correctly at scale, or use it to trace exactly what parameters a misfiring tag sent three hours ago.

Prerequisites: you need the GA4 BigQuery export enabled and linked to a BigQuery project. If you haven't set that up yet, see our guide on setting up the GA4 BigQuery export first. Intraday tables appear automatically once the daily export is active — no additional configuration required.


What intraday tables are — and how they differ from daily tables

When GA4's BigQuery export runs, it writes data into two types of tables in your dataset. Understanding the difference is essential before you start querying.

Table type 01

events_YYYYMMDD

The permanent daily table. Created once per day after midnight, containing all finalised event data for that date. This is what you query for historical analysis and reporting. Data in these tables is stable — it won't change after it's written.

Table type 02

events_intraday_YYYYMMDD

A temporary table containing today's events, refreshed continuously throughout the day — typically every 15–30 minutes. This is your real-time debugging surface. The table for today's date is deleted and replaced by the permanent events_YYYYMMDD table once the daily export completes.

The intraday table has the same schema as the daily table — same columns, same nested structures, same event parameters. Any query that works on a daily table works on an intraday table. The only differences that matter for debugging are the freshness (15–30 min lag vs. 24+ hrs) and the fact that the table is temporary.

Intraday data is not real-time. There's a 15–30 minute delay between an event firing on your site and appearing in the intraday table. This is far more useful than waiting for the daily export, but it means you won't see an event you fired 2 minutes ago. If you need truly instant feedback, use GA4 DebugView alongside BigQuery — they complement each other rather than replace each other.


Finding and querying your intraday table

In BigQuery, navigate to your GA4 dataset. You'll see tables listed alphabetically — look for events_intraday_ followed by today's date in YYYYMMDD format. If you don't see it, either the export hasn't run for the day yet (it starts generating after the first events arrive) or fewer than the minimum number of events have been collected.

The most reliable way to query it without hardcoding today's date is to use BigQuery's _TABLE_SUFFIX wildcard with FORMAT_DATE:

BigQuery SQL — Query today's intraday table dynamically

SELECT
  event_name,
  event_timestamp,
  user_pseudo_id
FROM
  `your-project.analytics_XXXXXXXXX.events_intraday_*`
WHERE
  _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
LIMIT 100

Replace your-project with your GCP project ID and analytics_XXXXXXXXX with your GA4 dataset name (visible in BigQuery's left panel). Replace 'Europe/London' with your timezone — this matters because GA4 partitions data by the property's reporting timezone, not UTC.

Save your dataset path as a variable in your queries. Typing your-project.analytics_XXXXXXXXX repeatedly is error-prone. In BigQuery, you can use a parameterised query or simply keep a text snippet with your dataset path to paste in. Some teams store frequently-used queries in BigQuery's Saved Queries feature so the path is always correct.


Debug workflow 1 — Verify a tag is firing at scale

The most common use case: you've just published a GTM container with a new event tag, and you want to confirm it's actually firing for real users — not just in your Preview session.

This query counts how many times each event has fired today, ordered by volume, so you can spot a new event appearing (or a previously healthy event that's dropped to zero):

BigQuery SQL — Event volume check for today

SELECT
  event_name,
  COUNT(*) AS event_count,
  COUNT(DISTINCT user_pseudo_id) AS unique_users
FROM
  `your-project.analytics_XXXXXXXXX.events_intraday_*`
WHERE
  _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
GROUP BY
  event_name
ORDER BY
  event_count DESC

Run this before your deployment to get a baseline, then run it again 30–45 minutes after publishing. Your new event should appear in the list. If it doesn't — or if a previously present event has disappeared — you have a firing problem to investigate.

To narrow it to a specific event and a specific time window after your deployment:

BigQuery SQL — Check a specific event since deployment time

SELECT
  COUNT(*) AS event_count,
  COUNT(DISTINCT user_pseudo_id) AS unique_users,
  MIN(TIMESTAMP_MICROS(event_timestamp)) AS first_seen,
  MAX(TIMESTAMP_MICROS(event_timestamp)) AS last_seen
FROM
  `your-project.analytics_XXXXXXXXX.events_intraday_*`
WHERE
  _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
  AND event_name = 'form_submit'
  AND TIMESTAMP_MICROS(event_timestamp) >= '2026-06-23 14:30:00 UTC' -- your deployment time

Note on event_timestamp: GA4 stores timestamps as microseconds since Unix epoch — so 1719148800000000, not a human-readable date. Always wrap it in TIMESTAMP_MICROS() to convert it before filtering or displaying.


Debug workflow 2 — Spot events firing too many times

If you suspect duplicate firing — a tag sending the same event multiple times per user action — BigQuery lets you confirm it at scale rather than trying to replicate it in your own session.

This query looks for users who triggered a specific event more times than is plausible in a single session. For a purchase event, more than one or two fires per user in a day is a red flag:

BigQuery SQL — Detect users with suspiciously high event counts

SELECT
  user_pseudo_id,
  COUNT(*) AS times_fired,
  ARRAY_AGG(
    TIMESTAMP_MICROS(event_timestamp)
    ORDER BY event_timestamp
  ) AS fire_times
FROM
  `your-project.analytics_XXXXXXXXX.events_intraday_*`
WHERE
  _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
  AND event_name = 'purchase'
GROUP BY
  user_pseudo_id
HAVING
  COUNT(*) > 1
ORDER BY
  times_fired DESC

The ARRAY_AGG of timestamps is the key part: it shows you the exact sequence of firing times for each user. If you see the same user firing purchase twice within 2 seconds, that's a GTM trigger problem. If the gap is 30 minutes, that might be a legitimate second purchase. The timestamps tell you which it is — something Preview mode alone can never do.

To go a step further and calculate the gap between the first and second firing, use a window function:

BigQuery SQL — Calculate time gap between duplicate events per user

WITH ranked_events AS (
  SELECT
    user_pseudo_id,
    event_timestamp,
    ROW_NUMBER() OVER (
      PARTITION BY user_pseudo_id
      ORDER BY event_timestamp
    ) AS event_rank
  FROM
    `your-project.analytics_XXXXXXXXX.events_intraday_*`
  WHERE
    _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
    AND event_name = 'purchase'
)
SELECT
  user_pseudo_id,
  TIMESTAMP_MICROS(
    MIN(CASE WHEN event_rank = 1 THEN event_timestamp END)
  ) AS first_fire,
  TIMESTAMP_MICROS(
    MIN(CASE WHEN event_rank = 2 THEN event_timestamp END)
  ) AS second_fire,
  ROUND(
    (MIN(CASE WHEN event_rank = 2 THEN event_timestamp END)
     - MIN(CASE WHEN event_rank = 1 THEN event_timestamp END))
    / 1000000, 1
  ) AS gap_seconds
FROM
  ranked_events
GROUP BY
  user_pseudo_id
HAVING
  COUNT(*) > 1
ORDER BY
  gap_seconds ASC

Events firing within 1–3 seconds of each other almost always indicate a GTM trigger problem. Events firing with a gap of several minutes or more are more likely to be real user behaviour worth investigating separately.


Debug workflow 3 — Validate event parameters are correct

A tag firing is only half the story. The other half is whether the parameters attached to that event are accurate. GA4's DebugView shows you parameters for your own session, but you can't use it to check whether transaction_id is being passed correctly for all purchases, or whether item_category is null for a specific product type.

BigQuery lets you inspect parameters across all events. GA4 event parameters are stored in a repeated event_params column — a nested array of key-value pairs. To query a specific parameter, you unnest it:

BigQuery SQL — Extract a specific event parameter for all instances of an event

SELECT
  event_timestamp,
  user_pseudo_id,
  MAX(CASE WHEN ep.key = 'transaction_id'
    THEN ep.value.string_value END) AS transaction_id,
  MAX(CASE WHEN ep.key = 'value'
    THEN ep.value.double_value END) AS order_value,
  MAX(CASE WHEN ep.key = 'currency'
    THEN ep.value.string_value END) AS currency
FROM
  `your-project.analytics_XXXXXXXXX.events_intraday_*`,
  UNNEST(event_params) AS ep
WHERE
  _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
  AND event_name = 'purchase'
GROUP BY
  event_timestamp, user_pseudo_id
ORDER BY
  event_timestamp DESC

This query returns one row per purchase event, with the key parameters spread into columns. Scan the results for:

  • transaction_id values that are null — your dataLayer push isn't passing it, or the GTM variable isn't reading it correctly.
  • transaction_id values that repeat across multiple rows — you have a deduplication problem (see our guide on fixing duplicate conversions).
  • order_value showing 0 or null — the value parameter isn't being passed or is being sent as a string when GA4 expects a number.
  • currency showing inconsistent values — GBP and gbp are treated as different currencies in GA4.

Understanding value types in event_params: GA4 stores parameter values as one of four typed fields — string_value, int_value, float_value, or double_value. If you query string_value for a numeric parameter, you'll get null even if the parameter was sent. Always check which type your parameter should be. Order values are typically double_value; event names and IDs are typically string_value.


Debug workflow 4 — Trace the full event sequence for a single user session

When a specific user reports a problem — a form submission that didn't register, a purchase that showed a confirmation page but isn't in GA4 — you can reconstruct their exact session in BigQuery using their client ID.

The client ID in GA4 is stored in the user_pseudo_id field. You can get it from the GA4 DebugView if the user is on a debug device, from a cookie (_ga cookie value), or from the GA4 Realtime report if you can identify the session.

BigQuery SQL — Full event timeline for a single user session

SELECT
  TIMESTAMP_MICROS(event_timestamp) AS event_time,
  event_name,
  MAX(CASE WHEN ep.key = 'page_location'
    THEN ep.value.string_value END) AS page,
  MAX(CASE WHEN ep.key = 'ga_session_id'
    THEN ep.value.int_value END) AS session_id
FROM
  `your-project.analytics_XXXXXXXXX.events_intraday_*`,
  UNNEST(event_params) AS ep
WHERE
  _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
  AND user_pseudo_id = '1234567890.0987654321' -- replace with the actual client ID
GROUP BY
  event_timestamp, event_name
ORDER BY
  event_timestamp ASC

The output is a chronological log of everything GA4 received from that user — every page view, every event, the page they were on when each event fired, and the session it belongs to. It's the closest thing to a server-side replay of a user's session that GA4 can provide.


A reusable GTM deployment health check query

Rather than writing queries from scratch after every GTM publish, it's worth building a single saved query that gives you a snapshot of tracking health at any point in the day. The query below compares today's intraday event volumes against yesterday's daily table — flagging events that have dropped or disappeared.

BigQuery SQL — Today vs yesterday event volume comparison

WITH today AS (
  SELECT
    event_name,
    COUNT(*) AS count_today
  FROM
    `your-project.analytics_XXXXXXXXX.events_intraday_*`
  WHERE
    _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE('Europe/London'))
  GROUP BY event_name
),

yesterday AS (
  SELECT
    event_name,
    COUNT(*) AS count_yesterday
  FROM
    `your-project.analytics_XXXXXXXXX.events_*`
  WHERE
    _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE('Europe/London'), INTERVAL 1 DAY))
  GROUP BY event_name
)

SELECT
  COALESCE(t.event_name, y.event_name) AS event_name,
  COALESCE(t.count_today, 0) AS count_today,
  COALESCE(y.count_yesterday, 0) AS count_yesterday,
  CASE
    WHEN y.count_yesterday = 0 THEN 'NEW EVENT'
    WHEN t.count_today IS NULL THEN 'MISSING TODAY'
    WHEN t.count_today < y.count_yesterday * 0.5 THEN 'DROP >50%'
    WHEN t.count_today > y.count_yesterday * 2.0 THEN 'SPIKE >2x'
    ELSE 'OK'
  END AS status
FROM
  today t
  FULL OUTER JOIN yesterday y USING (event_name)
ORDER BY
  status DESC, count_yesterday DESC

The status column does the diagnostic work for you. Run this query 30–60 minutes after any GTM container publish and scan the results. A MISSING TODAY flag on an event that was healthy yesterday means your latest container version broke something. A SPIKE >2x on a conversion event almost always means a duplicate firing problem that needs immediate attention.

Note on the today vs. yesterday comparison: today's intraday count will naturally be lower than yesterday's full-day count — the day isn't over yet. The thresholds in the CASE statement (0.5 for a drop, 2.0 for a spike) are deliberately generous to account for this. Tighten them as you get a feel for your own traffic patterns. Running the query at the same time of day (e.g. always at 2pm) makes the comparison more meaningful.


Limitations worth knowing

BigQuery intraday tables are powerful, but they have constraints that affect how you use them for debugging.

  • The 15–30 minute lag is real. If you publish a GTM container and immediately query the intraday table, you'll be looking at pre-deployment data. Wait at least 30 minutes and make sure you've had meaningful real-user traffic in that window before drawing conclusions.
  • Intraday tables are not guaranteed. Google's documentation notes that intraday export is offered on a best-effort basis. On rare occasions the table may be delayed or unavailable for part of the day. The daily export is guaranteed; the intraday one is not.
  • The table resets at midnight. The intraday table for today disappears when the permanent daily table is written. If you need to preserve intraday data for comparison later, create a snapshot using BigQuery's table copy feature before midnight.
  • Query costs apply. Every query you run against BigQuery scans data and incurs cost. For a typical GA4 dataset, a single intraday query costs fractions of a penny — but if you're running these checks continuously or automating them, set up a BigQuery budget alert so costs don't creep up unnoticed.
  • Some events have processing delays. App events, imported events, and events that go through GA4's validation pipeline may arrive later than standard web events. If you're debugging a tag that uses the Measurement Protocol rather than GTM directly, add extra wait time before querying.

Putting it together as a post-deployment routine

The most effective way to use intraday BigQuery debugging isn't ad hoc — it's as a structured check you run after every GTM container publish. A routine that takes five minutes and catches problems before they affect a full day of data is worth considerably more than an hour of investigation the following morning.

  1. 1
    Publish your GTM container and note the time Record the exact publish time. You'll use this to filter intraday data to events that occurred after the deployment — separating pre- and post-deploy behaviour.
  2. 2
    Wait 30–45 minutes Give the intraday table time to refresh with post-deploy data, and give real users time to generate enough events to be statistically meaningful.
  3. 3
    Run the health check query Run the today-vs-yesterday comparison query. Scan the status column for anything flagged as MISSING TODAY, SPIKE >2x, or NEW EVENT that you didn't intentionally add.
  4. 4
    Drill into any flagged events For anything suspicious, run the parameter validation query to check whether the event is firing with correct, complete parameters — or the duplicate detection query if the count looks inflated.
  5. 5
    Roll back or fix immediately if needed GTM's version history makes rolling back a publish a one-click operation. If BigQuery confirms a problem, don't wait — roll back to the last known-good version while you investigate, rather than letting bad data accumulate through the day.

Preview mode verifies your intent. BigQuery intraday data verifies reality. Used together, they cover the two failure modes that catch GTM implementations off guard: problems that don't show up in controlled testing, and problems that only emerge at scale. Both matter — and now you have the queries to catch them.

Want this set up for your GA4 property?

We build BigQuery pipelines and debugging workflows for GA4 implementations — including saved query libraries, scheduled health checks, and alerts when event volumes drop unexpectedly. Book a free 30-minute audit and we'll show you what your current setup is missing.