Tracking & Attribution

Why Does Page Caching Leave WooCommerce Order Attribution Fields Empty?

Order attribution fields empty with page cache on? How caching breaks WooCommerce's checkout script, how to fix it, and how to attribute orders anyway.

Quick answer

WooCommerce order attribution is filled in by a browser script at checkout. The server prints an empty placeholder into the checkout page, and the script writes the visitor's source into hidden fields from its own cookies. A page cache or speed plugin can serve a stale copy of that page, freeze its script settings, or hold the script back, so the fields go out empty and the order shows as Unknown. Exclude checkout from caching, or attribute the order from the session instead.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Page caching leaves WooCommerce order attribution fields empty because the fields are filled in by a browser script at the last moment, not by the server. A page cache or speed plugin can serve a stale checkout page, freeze the script’s settings from an earlier visitor, or hold the script back until after the order is placed. When that happens, the fields go out empty and the order is shown as Unknown.

The symptom is easy to spot. With caching on, a growing share of orders shows no origin. With caching off, the source comes back. The ads, the UTMs and the landing pages haven’t changed.

This article walks through how WooCommerce actually fills those fields, the three ways a caching layer breaks that, the exclusions that fix most of it, and how to attribute paid orders in a way a cached page can’t blank.

How does WooCommerce fill the order attribution fields?

WooCommerce prints an empty placeholder element into the checkout form, and a JavaScript file fills it in the browser. The script reads the visitor’s source from sourcebuster.js cookies, then writes it into hidden inputs inside that placeholder. When the order is placed, those hidden inputs are submitted with it, and WooCommerce saves them as the order’s origin.

The server-side part is small. WooCommerce’s OrderAttributionController prints an empty wc-order-attribution-inputs element on the checkout’s billing hooks. It also hands the script a settings object, including cookie lifetime, session length, and an allowTracking flag.

Everything else happens in the browser. The script, order-attribution.js, keeps a set of sbjs_ cookies (sbjs_current, sbjs_first, sbjs_session and others) that remember where the visitor came from. At checkout it copies that data into the hidden inputs.

If the inputs are empty, WooCommerce saves nothing. The controller returns early when every source value is empty, so the order has no attribution data, and the admin shows its origin as Unknown. That early return is why a failed handoff produces Unknown rather than a wrong source.

So the order is only as good as three things lining up at checkout: the placeholder exists, the script runs with the right settings, and it runs before the order is submitted.

What does page caching actually do to those fields?

Caching breaks at least one of those three things. It can serve checkout HTML that was generated for someone else, freeze the script’s settings at the moment the page was cached, or combine and delay the script so it hasn’t run when the buyer clicks Place order. Each of those produces an order with empty fields, and WooCommerce records it as Unknown.

Two checkout paths compared: on the left a cached checkout page serves frozen HTML, the attribution script is delayed, the hidden fields go out empty and the WooCommerce order is saved as Unknown; on the right the PartialLeads tag recorded the visitor's source on landing and the paid order is matched back to that session

Stale checkout HTML

A full-page cache stores the rendered HTML and serves it to the next visitor. Checkout and cart pages are meant to be dynamic, and most cache plugins exclude them by default. Host-level caches, CDN rules and custom templates can still end up caching them anyway. A cached checkout carries whatever markup and inline script settings were rendered for the first visitor.

Frozen script settings

The allowTracking flag, cookie lifetime and session length are printed into the page by the server. If a consent tool changes that flag per visitor, a cached copy serves one visitor’s answer to everyone. A page cached while tracking was off keeps it off for every buyer who gets that copy.

Delayed, deferred or combined JavaScript

This is the most common culprit, and it often ships in the same plugin as the page cache. Speed plugins delay scripts until the visitor scrolls or clicks, defer them, or merge them into one bundle. If the attribution script is held back, or its dependency on sourcebuster breaks inside a combined file, the hidden inputs are still empty when the form submits.

The public WooCommerce bug about Unknown orders names the same family of causes. The reporter pointed at “when WooCommerce Order Attribution JS runs”, session initialization and “interaction with themes that defer WooCommerce JS”, and wrote that “attribution may be finalized before UTM data is reliably available.” WooCommerce labels the issue a confirmed bug.

How do you confirm caching is the cause on your store?

Run the same tagged test order twice, once with caching and script optimisation on and once with them off, then compare the order’s origin. If only the cached run comes back Unknown, caching is your cause. Check the hidden inputs in developer tools before you submit each order, so you can see exactly which layer left them empty.

  1. Open a private window and land on yoursite.com/?utm_source=cachetest&utm_medium=cpc&utm_campaign=cache-on.
  2. Browse to a product, add to cart, go to checkout. Before you pay, open developer tools and find the wc-order-attribution-inputs element in the checkout form. Check whether its hidden inputs have values.
  3. View the page source of checkout. Look for your cache plugin’s signature comment or a cache-status header in the network tab. Its presence on checkout means checkout is being cached.
  4. Place the order with a cheap test product, then open it in the admin and read its origin.
  5. Purge the cache, switch off page caching and JS delay, and repeat with utm_campaign=cache-off.

If the inputs were empty in step 2 but the sbjs_ cookies exist, the script ran late or not at all. If the cookies are missing too, look at consent and blocking before blaming the cache.

A test order only proves the path you tested. Also compare Unknown share by day against the date you enabled or changed your caching setup; a step change on that date tells the same story at scale.

How do you stop caching from emptying the fields?

Keep checkout, cart and my-account out of every cache layer, and exclude the order attribution script and sourcebuster from JavaScript delay, defer and combine settings. Then purge everything and repeat the tagged test order. That fixes the cache-caused share of Unknown orders. It can’t fix orders from blocked browsers, declined consent, or checkouts that never load the storefront script.

Exclude the dynamic pages. Add checkout, cart and my-account to the page cache exclusions in your plugin, your host’s cache panel and your CDN rules. Check all three; a single one serving checkout is enough to break it.

Exclude the scripts from optimisation. In the JS delay, defer and combine settings, exclude WooCommerce’s order attribution script and the sourcebuster script. Search the exclusion field for the file names you saw in the checkout page source.

Don’t cache the settings object. If a consent tool toggles tracking per visitor, the pages that print the attribution settings shouldn’t be served from a shared cache.

Purge, then test again. Old cached copies stay live until they expire. Purge the page cache and any CDN cache before you judge the fix.

Keep WooCommerce current. Because the Unknown bug is confirmed, check the changelog for order attribution fixes before you rebuild anything.

The limit is that all of this still decides attribution in the browser, at the last second of checkout. You are making the handoff more reliable, not removing it. That is also why Unknown orders are a symptom worth watching after every plugin update, as the guide to why ad conversions show as Direct explains for the neighbouring failure.

How does PartialLeads attribute WooCommerce orders when caching empties the fields?

It doesn’t use those fields. The PartialLeads tag creates its visitor id in the browser and records UTMs, click ids, referrer and landing page when the visitor arrives. The paid order reaches PartialLeads through the WooCommerce plugin and webhook, and is matched back to that session by visitor id, then email, phone or IP. A cached checkout page has nothing to blank.

The visitor id is made in the browser, not printed into the page. The tag generates the id client-side and keeps it in localStorage with a first-party cookie as backup. A cached copy of your checkout can’t carry someone else’s id or a frozen empty value, because the server never wrote one into the HTML.

The source is recorded on landing. UTMs, gclid, fbclid and the other click ids, the referrer and the landing page are captured on the visitor’s first page, not carried through checkout fields at the end. If a visit has no UTMs, the referrer is used as the fallback. The explainer on first-party attribution covers why that record lives on your own domain.

The order is matched after it is paid. WooCommerce orders are sent to PartialLeads when they reach processing or completed; nothing fires on the thank-you page. The match runs in tiers: the visitor id echo first, then email, then phone, then IP. Sessions from the same person are stitched into one identity, as the explainer on how visitor identity resolution works describes.

Purchases ledger mockup for a WooCommerce store: paid orders listed with order number, matched or unmatched status, source badge and revenue, beside an Attribution report card showing first-touch and last-touch tables side by side grouped by source

Where you see it working. Take the order numbers WooCommerce shows as Unknown and find them in the Purchases ledger. Each paid order appears there as matched or unmatched. Matched orders carry their session’s source into the Attribution report, with first touch and last touch side by side and the PartialLeads Resolved model beside them. The guide to reading an attribution report explains how to read the three together. The same matched orders can be sent on to ad platforms, as the server-side WooCommerce purchase tracking guide walks through.

The honest constraints. The PartialLeads tag is JavaScript too. Keep it out of your speed plugin’s delay list, or a buyer who never scrolls may not be recorded until later in the visit. A browser that blocks the tag has no session to match by visitor id, so the order matches only if that person’s email or phone is already known. Unmatched orders stay in the Purchases ledger and can be matched later when identity arrives. Only paid orders are recorded. PartialLeads doesn’t write the source back into WooCommerce’s own fields, so WooCommerce’s origin column still shows what it shows.

What breaks The mechanism Where you see it in the dashboard
Cached checkout serves empty or stale attribution fields Paid order matched to the landing session via the WooCommerce plugin and webhook, not the checkout form Purchases ledger, matched rows
Speed plugin delays the attribution script past submit Source recorded by the tag when the visitor lands, not at checkout Attribution report by source, medium, campaign
Frozen consent or tracking settings in cached HTML Visitor id created client-side, never rendered into the page Journey timeline on the lead
No visitor id echo on the order Tiered fallback match by email, then phone, then IP First-touch and last-touch tables side by side
No session found at all Order kept as unmatched, re-matchable when identity arrives Revenue by Match Quality donut, unmatched slice

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources


Frequently asked questions

QWhy are WooCommerce order attribution fields empty only when caching is on?
Because the fields are filled by a browser script at checkout, and caching changes what reaches the browser. A cached checkout can carry stale markup or frozen script settings, and speed plugins in the same package often delay or combine the attribution script. If the script hasn't filled the hidden inputs when the order is submitted, WooCommerce saves no source and shows the order as Unknown.
QWhich pages should I exclude from caching on a WooCommerce store?
Checkout, cart and my-account at minimum. Exclude them in the cache plugin, in any host-level cache and in CDN rules, because one layer still caching checkout is enough to break it. Then exclude WooCommerce's order attribution script and the sourcebuster script from JavaScript delay, defer and combine settings, purge every cache, and place a tagged test order.
QDoes LiteSpeed Cache or WP Rocket break WooCommerce order attribution?
Not by default in every setup, but their JavaScript delay, defer and combine options can hold back or break the attribution script, and a misconfigured page cache can serve checkout. The fix is the same for any speed plugin: exclude the dynamic pages and the attribution scripts, purge, then compare a tagged test order with optimisation on and off.
QCan I get the source back for orders already saved as Unknown?
Not from WooCommerce itself. When the hidden fields arrive empty, WooCommerce saves no attribution data at all, so there is nothing to restore. You can only attribute those orders if another system recorded the visitor's session independently and can match the order back to it, for example by email or phone.
QWill fixing my cache settings bring Unknown orders to zero?
No. Caching is one cause among several. Buyers who block scripts, decline tracking consent, pay through a flow that skips the classic checkout form, or have orders created in the admin will still produce orders with no browser attribution data. Fixing the cache removes the share it caused, which you can measure by comparing Unknown share before and after.
QIs the PartialLeads tag affected by JavaScript delay plugins too?
It can be, because it is also a browser script. If a speed plugin delays it until the visitor interacts, a buyer who never scrolls may not be recorded on landing. Exclude the tag from delay settings. Orders from visits the tag missed can still match by email, phone or IP when that person is already known, but some will stay unmatched.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.