Pricing

Why Your GA4 Numbers in Google Sheets Don't Match GA4: The 3 Real Reasons (and How to Fix Each)

Sessions48,291+14.2%Bounce Rate38.4%−3.1ppAvg Duration2m 41s+0:08Conversions1,284+22.7%Sessions over 30 days
JW
James Whitfield

GA4 numbers don't match between the UI and Google Sheets? Here are the 3 exact technical causes, reporting identity, Google Signals thresholding, and sampling, and how to fix each.

GA4 data in Google Sheets does not match the GA4 UI because the two surfaces use different default settings. The three most common causes are: a reporting identity mismatch between Blended (UI default) and Device-based (API default), Google Signals thresholding that removes rows from one report but not the other, and sampling that triggers on large API queries but not in the UI. Each has a specific, documented fix.

Why This Problem Is So Frustrating

Few reporting problems erode trust in data faster than pulling numbers into a spreadsheet and watching them disagree with the tool that generated them.

"I exported last month's sessions from GA4 into Sheets and the number was different from what I saw in the GA4 UI literally two minutes earlier. How is that possible?" (r/analytics, 2024)

"We built an entire dashboard in Google Sheets using the GA4 API and now our client is asking why the numbers don't match what they see in their GA4 account. I have no good answer." (Marketing agency Slack community)

The maddening part is that neither number is necessarily wrong. They are correct answers to slightly different questions. Understanding which question each surface is answering is the key to fixing the discrepancy, and to explaining it to a client without losing credibility.

There are three independent technical causes. They can occur separately or all at once. This guide addresses each one specifically.

Reason 1: Reporting Identity Mismatch

What it is

GA4 supports multiple "reporting identities" that control how users and sessions are counted when someone visits your site across multiple devices or after clearing cookies.

The GA4 UI defaults to Blended identity, which combines User-ID (if you send it), Google Signals cross-device data, and device/cookie-based identity in that priority order. The GA4 Data API (which powers any Google Sheets integration) defaults to Device-based identity, which uses only the device cookie (client_id).

This means the UI and the API are counting users through different lenses. Blended identity can merge sessions that Device-based identity counts separately, resulting in lower user counts in the UI compared to the API, or vice versa depending on your audience and whether Google Signals is active.

How to confirm this is your problem

Open GA4 Admin > Property Settings > Reporting Identity. Note which identity is selected. Then check what identity your Sheets integration is requesting. If they differ, this is likely your cause.

If you are using the GA4 Data API directly, check whether your requests include a metricAggregations or keepEmptyRows parameter, but more relevantly, whether you are explicitly setting returnPropertyQuota or any identity-related field. By default the API returns Device-based results even when your property is configured to use Blended.

The fix

Option A: Align the API to the UI setting

When making API requests, explicitly set the reporting identity in your request body:

json
{
  "reportRequests": [
    {
      "property": "properties/YOUR_PROPERTY_ID",
      "dateRanges": [{ "startDate": "30daysAgo", "endDate": "today" }],
      "dimensions": [{ "name": "sessionDefaultChannelGroup" }],
      "metrics": [{ "name": "sessions" }]
    }
  ]
}

The GA4 Data API v1 does not currently expose a direct reportingIdentity parameter in the request body. To match Blended identity from the API, you need to configure the property-level default in GA4 Admin. If your property is set to Blended and your API requests are returning Device-based numbers, the discrepancy is the property vs. API default gap.

Option B: Change the UI to Device-based

Navigate to Admin > Property Settings > Reporting Identity and switch the UI to Device-based. This makes both surfaces consistent and removes any Google Signals dependency from your user counts. It will change your UI numbers, not your API numbers.

Option C: Use the same Reporting Identity consistently

For most teams running Sheets dashboards, setting the GA4 property to Device-based is the simplest path. Blended identity adds complexity and its cross-device matching is probabilistic, not deterministic, which means neither number is precisely "right" anyway.

Reason 2: Google Signals Thresholding

What it is

When Google Signals is enabled on a GA4 property, Google applies a privacy threshold to protect individual user identity. When a dimension combination (e.g., city + device category + age bracket) has fewer than a minimum number of users, GA4 removes that row from the report entirely rather than showing a small number.

The critical detail: thresholding applies differently depending on which surface you are using and which dimensions you request. The GA4 UI sometimes aggregates rows differently than the API, meaning a row that survives the threshold in one context gets removed in another. The result is that row counts differ between the UI and API, and total metrics differ because the removed rows' data is excluded, not reassigned.

This is different from the (other) cardinality bucket (covered below). Thresholding silently removes rows. The cardinality bucket consolidates them under a label. Both affect totals but in different ways.

How to confirm this is your problem

Look for dimension combinations in your Sheets report where rows appear in the UI but not in the API export (or vice versa). If the missing rows tend to be geographic, demographic, or interest-based dimensions (all of which rely on Google Signals) thresholding is the likely cause.

You can also check: Admin > Property Settings > Google Signals Data Collection. If it is enabled, thresholding can apply to any report using those dimensions.

The fix

Option A: Disable Google Signals

Go to Admin > Property Settings > Google Signals Data Collection and turn it off. This eliminates thresholding entirely. The tradeoff is losing cross-device reporting and demographic/interest data. For most analytics reporting use cases this is an acceptable tradeoff.

Option B: Remove thresholding-sensitive dimensions

Stop requesting dimensions that depend on Google Signals (age, gender, interests, city-level geography with small populations). Stick to session-level dimensions like channel, source/medium, landing page, and device category. Your totals will match because no rows are being thresholded out.

Option C: Accept and document the discrepancy

If you need demographic dimensions and Google Signals, document the known discrepancy and include a note in your Sheets dashboard explaining that row-level data may differ from UI reports due to privacy thresholding. This is the intellectually honest approach when the data is worth keeping despite the noise.

Reason 3: Sampling on Large Queries

What it is

GA4 applies sampling to reports when the query exceeds a certain event volume threshold. Sampled reports use a subset of your data to estimate the full result, which means the returned numbers are approximations.

Sampling triggers differently in the GA4 UI versus the API. The UI uses a 10-million-event threshold before sampling kicks in for standard reports. The API has its own thresholds and can return sampled data in a different proportion than the UI. If you are pulling a wide date range or using many dimensions simultaneously, one surface may be sampled when the other is not, or both may be sampled at different rates.

The GA4 UI shows a yellow shield icon when a report is sampled. The API returns a samplingSpaceSizes and samplesReadCounts field in the response metadata, but most Sheets integrations do not surface this to the user. So you get numbers that are both wrong, in different ways, with no visible warning in your spreadsheet.

How to confirm this is your problem

In the GA4 UI, check for the sampling indicator (yellow shield, usually visible in the top right of a report). For the API, inspect the raw API response for metadata.samplingSpaceSizes, if this field is present and populated, the response is sampled.

Sampled discrepancies tend to be larger for long date ranges, many dimensions in a single query, and properties with high event volume.

The fix

Option A: Reduce query scope

Split your date range into smaller chunks (weekly or daily slices) and run multiple API requests, then concatenate results in Sheets. This reduces the event count per query and can keep each request below the sampling threshold.

Option B: Use GA4 360 (if available)

GA4 360 raises the unsampled threshold significantly. If your organization has access, queries that trigger sampling on standard GA4 may return unsampled results on 360.

Option C: Export to BigQuery

GA4's BigQuery export is always unsampled. If accurate data at scale is a hard requirement, the correct architecture is GA4 → BigQuery → Sheets (via a BigQuery data connector or QUERY function). This adds complexity but gives you exact numbers every time.

Option D: Use Data API with quotas in mind

The GA4 Data API has a per-day quota on response rows. Breaking queries into smaller date ranges increases the number of API calls but reduces sampling. Most third-party Sheets connectors do this automatically.

Bonus: The (Other) Cardinality Bucket

This is a fourth issue that produces a different symptom but is worth understanding alongside the above.

GA4 limits the number of unique values it tracks per dimension to 500 in standard reports. When a dimension exceeds 500 unique values (common with page paths, campaign names, or custom dimensions), GA4 consolidates excess values into a row labeled (other). This row appears in both the UI and API, but the values consolidated into it can differ between surfaces depending on how the report is built.

The result: individual dimension values match, but totals seem off because (other) represents different underlying data in each context.

The fix: Reduce dimension cardinality. Use cleaner URL structures, avoid high-cardinality custom dimensions as primary breakdowns, or filter your reports to a smaller value set. The (other) bucket cannot be eliminated, but it can be minimized.

Comparison: What Each Fix Changes

CauseUI Numbers Change?API Numbers Change?Recommended Fix
Reporting identityYes (if you switch to Device-based)NoSwitch UI to Device-based
Google Signals thresholdingYes (rows restored if Signals disabled)Yes (rows restored)Disable Signals or remove sensitive dims
SamplingDepends on UI sampling stateYes (break into smaller queries)Smaller date ranges or BigQuery
(Other) cardinalityYes (affects row breakdowns)YesReduce dimension cardinality

Troubleshooting Decision Tree

Step 1: Are the numbers off by a consistent percentage (e.g., always ~8% lower in Sheets)? → Likely a reporting identity mismatch. Check Reason 1.

Step 2: Are specific rows missing from Sheets but present in the UI (especially geographic or demographic rows)? → Likely Google Signals thresholding. Check Reason 2.

Step 3: Do the discrepancies appear only for long date ranges or complex dimension combinations? → Likely sampling. Check the API response metadata and try Reason 3 fixes.

Step 4: Is there an (other) row with a large value in either surface? → Cardinality is a contributing factor. Reduce dimension breadth.

Step 5: Is your Sheets integration using a stale data connection or cached results? → Force a refresh. Some connectors cache for hours. This is less common but worth ruling out first.

Bottom Line

GA4 numbers disagree with Google Sheets for three specific, fixable reasons. Reporting identity is the most common and least obvious. The UI and API simply default to different identity models. Google Signals thresholding removes rows from certain dimension combinations to protect user privacy, and the effect can differ between surfaces. Sampling introduces statistical approximation on large queries, and the sampling rate can differ between the UI and the API.

Fix them in order of likelihood: check identity first, then look for missing rows tied to demographic or geographic dimensions, then investigate date range scope if numbers are off on complex queries. In most cases, switching the property to Device-based identity and disabling Google Signals resolves 80% of discrepancies immediately.

Get GA4 Data Into Sheets That Actually Matches

brooked.io connects GA4 to Google Sheets with explicit identity configuration, automatic date range chunking to minimize sampling, and clear indicators when thresholding affects your data. Stop debugging discrepancies and start trusting your reports.

Related reading:

  • GA4 Custom Dimensions in Google Sheets: The customEvent API Format Nobody Documents
  • How to Sync HubSpot Deals and Pipeline Data to Google Sheets in Real Time

Frequently asked questions

Why does GA4 show different session counts than Google Sheets even for yesterday's data?

The most likely cause for a single-day comparison is reporting identity. The UI uses Blended identity by default; the API uses Device-based. On recent dates where session stitching across devices is more likely to be applied, the gap can be noticeable even for simple metrics like sessions or users.

What is the Google Signals threshold exactly?

Google does not publish the exact minimum user count that triggers thresholding. It varies based on property volume and is applied automatically. The threshold is generally understood to be in the range of a few hundred users per dimension combination, but the exact number is not documented.

Does sampling affect all GA4 properties equally?

No. Standard GA4 properties are more susceptible to sampling than GA4 360 properties. Properties with higher daily event volumes hit thresholds faster. Using fewer dimensions in a single query and shorter date ranges both reduce sampling likelihood.

If I fix the reporting identity, will my historical data in Sheets change?

Yes. If you re-pull historical data after aligning the identity setting, the numbers will reflect the new identity model for that historical period. GA4 reprocesses the underlying data using the selected identity, so the same date range can return different numbers depending on which identity is active when you query it.

Can a third-party Sheets connector fix these issues automatically?

Partially. A good connector like brooked.io can handle identity alignment (by letting you configure which identity to use), break large queries into smaller date ranges to avoid sampling, and surface thresholding warnings. However, the underlying GA4 data limitations are set by Google, no connector can circumvent Google Signals thresholding or un-sample data that GA4 has sampled at the source.

Ready to connect your data to Google Sheets?

Brooked's free tier covers 100 imports per month with AI Analyst included, no credit card required.

Install Brooked free →

Also in Analytics & BI

More Analytics & BI guides

Analytics & BI

How to Connect GA4 to Google Sheets

The free official Google add-on (GA4 Reports Builder) with the scheduling step nobody documents, the native Connected Sheets GA4 connector, BigQuery export for unsampled event-level data, plus the sampling and '(other)' aggregation traps that make GA4 numbers in your sheet diverge from the UI.

JW
James Whitfield
Read
Analytics & BI

How to Connect Looker to Google Sheets

All 4 ways to get Looker data into Google Sheets: Connected Sheets, scheduled delivery, API, or a no-code add-on.

JW
James Whitfield
Read

Get your spreadsheet hours back

Brooked sets up in seconds. Your team is querying live data before lunch.

Get started free