TL;DR
Every Shopify merchant running GA4 sees "(not set)" in their source/medium, campaign, or landing page reports. It is not a tracking error. It is GA4's way of saying "I received this event, but I do not have the information needed to fill this dimension." On Shopify, the most common cause is purchase events that arrive without a browser session: admin orders, POS sales, subscription renewals, Shop Pay express checkouts, and external payment redirect flows. Some of these can be addressed with server-side tracking. Others are structurally unattributable because no browser was ever involved. This article walks through each cause, explains which ones are fixable, and provides a realistic benchmark for how much "(not set)" is normal.
Key Takeaways
- "(not set)" is GA4's placeholder when it does not have data for a requested dimension. It is not a bug, an error, or a sign that tracking is broken.
- Five structural causes produce "(not set)" on Shopify purchase events: admin/POS orders, subscription renewals, Shop Pay express checkout, PayPal Express redirects, and Stripe API-created orders.
- Server-side tracking can assign explicit source/medium values (like "(direct)" / "(none)") to orders that have no browser session, replacing "(not set)" with a known category.
- The campaign_details event, sent server-side before the purchase event, is the mechanism that sets session attribution for server-side hits.
- A "(not set)" rate of 5-15% is normal for stores using Shop Pay. Stores with significant admin, POS, or subscription order volume will see higher rates.
What does "(not set)" actually mean in GA4?
"(not set)" is GA4's default placeholder value. It appears in any report dimension where GA4 does not have data at the time the event was processed.
This is distinct from "(direct) / (none)," which is an explicit attribution. "(direct) / (none)" means GA4 saw the session but could not find a campaign, referral, or organic search source. "(not set)" means GA4 did not even have enough context to make that determination.
The difference matters. "(direct) / (none)" means a browser session existed but had no attributable source. "(not set)" means there was no session context at all, or the dimension was simply not populated on the event.
You will see "(not set)" in multiple GA4 dimensions: session source/medium, session campaign, landing page, device category, country. Each has its own cause. This article focuses on the most impactful one for Shopify merchants: "(not set)" on purchase events in source/medium and campaign dimensions.
The five structural causes on Shopify
Not every "(not set)" purchase has the same root cause. On Shopify, five specific order types produce events without browser sessions. Understanding which ones apply to your store determines how much of your "(not set)" is fixable.
Cause 1: Admin, POS, and draft orders
Orders created in Shopify admin, at a POS terminal, or through draft orders have no browser checkout session. A staff member or the POS device created the order. No customer browsed the storefront, no pixel fired, no cookies were set.
When a tracking app receives the orders/create webhook for these orders, it needs to send a purchase event to GA4 via the Measurement Protocol. But there is no browser _ga cookie to reference. The app generates a synthetic client_id (typically in a format like server.{customerId}) so that GA4 accepts the event. This synthetic client_id does not match any existing browser session, so GA4 creates a new user record with no session history.
Server-side tracking can improve this by setting explicit attribution values. Instead of leaving the source/medium empty (which produces "(not set)"), a well-built tracking implementation sends a campaign_details event before the purchase event with source set to "(direct)" and medium set to "(none)." This replaces "(not set)" with a known category. The order is still unattributable to a marketing campaign, but it is correctly categorized rather than left as a data gap.
WeltPixel Conversion Tracking handles admin and POS orders on the Plus plan, sending them to GA4 with explicit (direct) / (none) attribution. The admin and POS tracking guide covers this in detail.
Cause 2: Subscription renewal orders
Subscription apps (Shopify Subscriptions, Recharge, Bold Subscriptions) create renewal orders via the Shopify Admin API. These are automated, server-initiated transactions. No customer opened a browser. No session exists.
The same dynamic applies as admin orders: the webhook fires, a tracking app can send the event server-side, but there is no browser context to reference. The synthetic client_id creates a disconnected GA4 user record.
These orders are structurally unattributable. The original subscription sign-up may have come from a Facebook ad six months ago, but the monthly renewal has no connection to that session. GA4 does not have a mechanism to carry attribution forward across API-created repeat orders.
For stores with significant subscription revenue, this is often the largest single contributor to "(not set)" purchase events. It is not a tracking failure. It is a structural reality of how subscription billing works on Shopify.
Cause 3: Shop Pay express checkout
Shop Pay's accelerated checkout can bypass the standard checkout flow. When a customer uses Shop Pay on a product page or in the cart, the checkout process can skip your storefront's standard pixel initialization sequence.
The result is that the purchase event arrives with a server-generated client_id instead of the browser's _ga cookie value. GA4 receives the purchase but cannot connect it to the browsing session that preceded it.
This is the most common cause of "(not set)" for standard online orders. The customer was on your site, they did browse, they did have a session with attribution data. But the Shop Pay redirect created a gap between the browser session and the server-side purchase event.
Server-side tracking mitigates this by sending a campaign_details event with the session's source/medium data before the purchase event. The Shop Pay tracking guide covers the full mechanism and remaining edge cases.
Cause 4: PayPal Express redirects
PayPal Express redirects the customer off your domain to paypal.com for payment, then back to your store for confirmation. This redirect chain can break session continuity in two ways.
First, the _ga cookie set on your domain is not available on paypal.com. When the customer returns, the browser may assign a new _ga value depending on cookie policy and ITP restrictions, creating a new GA4 session.
Second, the referral from paypal.com can overwrite the original session's source/medium. A customer who arrived from a Google Ads click may end up attributed to paypal.com / referral after the redirect, which is even more misleading than "(not set)."
The fix for the referral overwrite is to add paypal.com to your GA4 referral exclusion list (GA4 Admin > Data Streams > Configure tag settings > List unwanted referrals). This does not fix the session break, but it prevents PayPal from replacing the original attribution.
Cause 5: Stripe subscription auto-renewals
Stores using Stripe directly for recurring billing (outside of Shopify's native subscription system) face the same issue as Cause 2. Stripe creates orders via API when a subscription renews. No browser session, no cookie, no attribution context.
If these orders are synced back to Shopify as fulfilled orders, they appear in Shopify's revenue reports. When a tracking app picks them up from the orders/create webhook, the purchase event goes to GA4 with a synthetic client_id and no session attribution.
Like subscription renewals through Shopify apps, these are structurally unattributable. The original conversion may have happened months ago through any channel. The renewal is a financial event, not a marketing touchpoint.
How server-side tracking handles "(not set)"
The core mechanism is the campaign_details event. When a tracking app sends a purchase event to GA4 via the Measurement Protocol, it can precede it with a campaign_details event that sets the session's source, medium, and campaign values.
WeltPixel Conversion Tracking sends this campaign_details event 1 microsecond before the purchase event (using timestamp_micros set to Date.now() * 1000 - 1000). This ensures GA4 processes the attribution data before the purchase event, so the purchase lands in a session with known attribution.
When no real attribution data exists (admin orders, subscription renewals, API-created orders), the tracking explicitly sets source="(direct)" and medium="(none)" instead of leaving the fields empty. This is the difference between "(not set)" and "(direct) / (none)" in your reports. Both mean "no marketing campaign drove this specific order," but "(direct) / (none)" is an explicit categorization while "(not set)" is a data gap.
The Measurement Protocol guide explains how server-side events reach GA4 and why client_id matters.
How much "(not set)" is normal?
There is no universal threshold, but here are realistic benchmarks based on store type:
Standard online-only store with Shop Pay enabled: 5-15% of purchase events may show "(not set)" in source/medium dimensions. Shop Pay express checkout is the primary contributor. Server-side tracking with proper campaign_details events reduces this to the low end of the range.
Store with admin/POS orders: Add the percentage of your orders created through admin, POS, or draft orders. If 20% of your orders come through these channels, expect 15-30% "(not set)" without server-side admin order tracking.
Store with subscription orders: Add the percentage of orders that are automated renewals. A store where 40% of revenue comes from subscription renewals will see 35-50% "(not set)" because those orders are structurally unattributable regardless of tracking setup.
Store with all three: The rates compound. A store with Shop Pay, admin orders, and subscriptions can legitimately see 40-60% of purchase events with "(not set)" or "(direct) / (none)" attribution, and that is not a sign of broken tracking. It is a reflection of how those orders are created.
How to diagnose your "(not set)" in GA4
Step 1: Quantify the volume. In GA4, go to Reports > Acquisition > Traffic acquisition. Look at the "(not set)" row in the session source/medium table. Note its share of purchase events and revenue.
Step 2: Segment by order source. Cross-reference the "(not set)" purchase events in GA4 with your Shopify order timeline for the same period. Filter Shopify orders by source: how many came from admin, POS, draft orders, or subscription apps? If those numbers roughly match your "(not set)" count, the cause is structural.
Step 3: Check for Shop Pay volume. In Shopify Analytics, look at checkout completion by payment method. If Shop Pay is a significant share, some "(not set)" comes from express checkout sessions where the browser cookie was not carried through.
Step 4: Verify server-side attribution. In GA4 > Reports > Real-time, place a test order and watch whether the purchase event arrives with source/medium populated or empty. If it arrives with "(direct) / (none)," your server-side tracking is correctly assigning fallback attribution. If it arrives with "(not set)," the campaign_details event is not firing or not arriving before the purchase event.
What you can fix vs what is structural
| Cause | Fixable? | How |
|---|---|---|
| Admin/POS/draft orders showing "(not set)" | Yes | Server-side tracking with explicit (direct)/(none) attribution |
| Subscription renewals unattributed | No | Structurally no browser session exists |
| Shop Pay session breaks | Partially | Server-side campaign_details event recovers most sessions |
| PayPal referral overwrite | Yes | Add paypal.com to GA4 referral exclusion list |
| Stripe API renewals unattributed | No | Same as subscription renewals |
The honest answer is that some "(not set)" will always exist on Shopify. The goal is to minimize the fixable portion and correctly categorize the structural portion so your reports separate "unattributable" from "unknown."
FAQ
Is "(not set)" the same as "(direct) / (none)"?
No. "(not set)" means GA4 had no data for the dimension. "(direct) / (none)" means GA4 had a session but found no campaign, referral, or organic source. The first is a data gap. The second is an explicit classification.
Does "(not set)" mean my tracking is broken?
Not necessarily. If your "(not set)" volume roughly matches your admin + POS + subscription + Shop Pay order volume, your tracking is working correctly. The orders are structurally unattributable. If "(not set)" exceeds what those order types explain, investigate whether your browser pixel or server-side events are misconfigured.
Can I retroactively fix "(not set)" in GA4?
No. GA4 does not allow retroactive changes to event data. Once an event is recorded with "(not set)" attribution, it stays that way. Fixes only apply to events going forward.
Read Next
- Shopify Analytics vs GA4: Why Your Revenue Numbers Never Match covers the six structural causes behind every Shopify-GA4 revenue discrepancy, including how "(not set)" orders contribute.
- Admin and POS Order Tracking on Shopify explains how server-side tracking captures orders that never touch a browser.
- Shop Pay + Conversion Tracking: What Breaks, Why, and How to Fix It details the specific mechanisms that cause Shop Pay to break session continuity.
- GA4 Measurement Protocol on Shopify covers the server-to-server API that delivers purchase events when browsers cannot.
Sources
(No external sources cited.)