Skip to content
Gaurav TiwariDocs

Keep Carts and Checkouts Out of the Cache

Version 1.3.6 Updated October 26, 2026

The failure mode this exists to prevent: a shared cache serves one customer’s cart, checkout or account page to another customer. It is rare, it is catastrophic, and it is entirely preventable.

GT Performance ships adapters for FluentCart, Easy Digital Downloads and WooCommerce. All three are on by default under Integrations > Commerce safeguards, and each one contributes rules only while its plugin is active.

What an adapter contributes

Each adapter declares the state that must never be shared:

  • Dynamic paths: fixed defaults plus the permalinks of the pages you assigned in the store’s settings
  • Session cookies: the cookie prefixes that mark a customer with state
  • Query parameters: actions, order keys and coupon parameters
  • Product types: which posts are products, so a product change purges the right listings
FluentCartEasy Digital DownloadsWooCommerce
Detected byFLUENTCART_VERSION or FluentCart’s classesEDD_VERSION or edd_get_option()WC_VERSION or the WooCommerce class
Paths/cart/, /checkout/, /account/, /receipt/, /wp-json/fluent-cart/, plus the cart, checkout, customer profile and receipt pages from FluentCart settings/checkout/, /purchase-confirmation/, /purchase-history/, plus the purchase, success, failure and purchase history pages from EDD settings/cart/, /checkout/, /my-account/, /wp-json/wc/store/cart, /wp-json/wc/store/checkout, plus the cart, checkout and My Account pages from WooCommerce settings
Cookiesfct_, fct_cart_hashedd_items_in_cart, edd_session_, edd_cart_messages, edd_purchase, edd_cart_fees, edd_resume_payment, edd_cart, cart_discounts, preset_discount, edd_cart_token, edd_saved_cartwoocommerce_items_in_cart, woocommerce_cart_hash, wp_woocommerce_session_, woocommerce_recently_viewed, store_notice
Query parametersfluent-cart, fct, couponsedd_action, discount, payment-confirmation, purchase_keywc-ajax, add-to-cart, order-pay, order-received, key
Productsfluent-products, fluent-product, fct_productdownloadproduct, product_variation

The core defaults already bypass add-to-cart, wc-ajax and fluent-cart whether or not an adapter is active. Paths match on segment boundaries, so /checkout/ also protects /checkout and everything below it. See Cache Exclusions.

Where the rules apply

The adapters’ rules are merged into one policy, and that policy feeds every layer:

  • The drop-in. They are compiled into the encrypted config advanced-cache.php reads, so a cart request is refused before WordPress loads.
  • WordPress. The same rules decide whether a rendered page is stored.
  • Response headers. A front-end request that matches a commerce (or custom) bypass path, cookie or query rule, or that sends a query parameter as an array (name[]=), is sent with Cache-Control: no-store, private, max-age=0, CDN-Cache-Control: no-store, Cloudflare-CDN-Cache-Control: no-store and X-GT-Commerce-Cache: BYPASS. This runs even when the origin page cache is off or in optimize-only mode, so any CDN, proxy or host cache in front of the site is told not to store the page.
  • Cloudflare. The managed cache rule is compiled from the same policy, so the edge excludes the same paths, cookies and parameters as the origin. There is no second list to keep in step by hand.
  • Speculative loading. Cart, checkout and account paths are excluded from WordPress speculative loading (6.8+), so hovering a link never starts a session or spends a PHP worker on a page that is never cached. With plain permalinks, links carrying add-to-cart, remove_item, edd_action, key and similar action parameters are excluded too.

Two optimizer safeguards come with them:

  • JavaScript whose URL contains checkout, payment or cart is never minified, deferred or delayed. See JavaScript Optimization.
  • While Automatic conflict protection is on, stylesheets from the active commerce plugin (/plugins/woocommerce/, /plugins/easy-digital-downloads/, /plugins/fluent-cart/, /plugins/fluent-cart-pro/) are left out of unused-CSS pruning. Their cart, modal and validation states do not exist in the initial HTML. See CSS Optimization.

Invalidation

A cached product page must not keep advertising an old price or an in-stock badge.

Product saves. Saving a product purges and then preloads its permalink, the home page, its post type archive and its term archives. WooCommerce adds the shop page, and a variation purges its parent product.

Price, stock and sale changes. These go through the store’s own CRUD layer and never reach save_post, so GT Performance listens to the store’s hooks:

  • WooCommerce: woocommerce_product_set_stock, woocommerce_variation_set_stock, woocommerce_product_set_stock_status, woocommerce_variation_set_stock_status, woocommerce_product_object_updated_props, woocommerce_scheduled_sales
  • Easy Digital Downloads: edd_update_product_price
  • FluentCart: fluent_cart/product_updated

These hooks fire after the store has written the new price or stock, which is what makes their purge the one that counts: the post save purges earlier, before the price is written, so a visit in between can cache the old price again. Each one purges, straight away, at the origin and then at the edge (batched once per request):

  • the product’s page, and its parent’s for a variation;
  • the adapter’s related store pages: the home page, the product archive (WooCommerce’s shop page) and the product’s category and tag archives.

Every other shop page, grid and landing page that recorded the product or a listing it belongs to is purged too. Stock can add a product to or remove it from a filtered listing, so these count as membership changes. Those dependency purges run as background purge_url jobs at the highest queue priority, because a bulk stock sync can touch thousands of products, and they keep running when optional queue work is paused. See Purging.

Policy changes

The active adapters’ combined policy is hashed on every request (at init). When it changes, because you activated WooCommerce or a store update moved a page, the runtime config is recompiled so the drop-in enforces the new rules. Activating or deactivating any plugin recompiles it too.

This matters because commerce plugins do move their routes between versions, and a cache rule written for the old paths would silently start caching the new ones.

The Cloudflare rule is not rewritten automatically. After a commerce change, run Connect/sync Cloudflare (or wp gt-performance cloudflare sync). The rule preview on the Cloudflare tab, and wp gt-performance cloudflare plan, report managed-rule drift when the live rule no longer matches the policy. See Cloudflare.

If your commerce plugin has no adapter

Under Exceptions > Cache exceptions:

  1. Add its cart, checkout and account paths to Paths that must stay dynamic.
  2. Add its session cookie prefix to Cookie prefixes that bypass cache.
  3. Add its action and order parameters to Never cache query parameters.

Save, then sync Cloudflare if you use it. Check each URL:

wp gt-performance cache explain --page-url=https://example.com/basket/

cacheable must be false, with a path: reason. For the cookie, request a normal page with the cookie set and confirm the response carries X-GT-Commerce-Cache: BYPASS:

curl -sI -H 'Cookie: shop_session_abc=1' https://example.com/ | grep -i '^x-gt'

Do not rely on the unknown_query rule alone. It bypasses unrecognized query parameters, but a cart page reached at a clean URL with a session cookie needs the cookie rule to be right.

To switch an adapter off (for example, a store plugin that is installed but not used for checkout), clear its checkbox under Integrations > Commerce safeguards.

Share

Was this helpful?

Still stuck? Open a support ticket