The Problem
A client running WooCommerce 10.8 with the cart block pinged me last week about a checkout drop-off pattern that did not match anything in their funnel. A shopper adds three items, hits /cart, sees a "Click here to log in" prompt at the top of the cart block, signs in to claim their member discount, and lands back on /cart with an empty cart. Mini-cart icon shows zero. Logging out and back in does not bring the items back.
I reproduced it locally on a clean copy of the theme. Exact recipe:
- Add three products as a guest from
/shop - Visit
/cart, click the login link inside the cart block notice - Authenticate on
/my-account - Get redirected to
/cart - Cart block renders empty, Store API confirms it
The Network tab confirms /cart returns a 200 with the cart block markup, but the Store API call returns this:
{
"items": [],
"items_count": 0,
"items_weight": 0,
"totals": { "total_price": "0", "currency_code": "USD" },
"errors": []
}
So the cart really is empty server-side after login. Not a render bug. Not a hydration glitch. The session itself dropped its contents the moment the guest authenticated.
Why It Happens
WooCommerce stores guest cart data against an anonymous session row keyed by a cookie called wp_woocommerce_session_<hash>. When a guest logs in, WordPress regenerates the auth cookies and WooCommerce migrates the session from the guest key to a user key by reading the saved user cart, merging the guest cart in, and writing the result back.
The merge is where the data vanishes. Two things have to be true for it to succeed under WooCommerce 10.8 with the cart block:
- The session handler has to load the guest cart before the new user is set on the request
- The cart block must not have already initialised an empty cart for the logged-in user during the same request
In a default theme on a default install both hold. On a real production site neither does, because something almost always calls WC()->cart->get_cart() early. A header mini-cart counter, an upsell widget, a custom hook in functions.php that tries to count items for a sticky banner. That early call instantiates the cart for the now-logged-in user, which has no saved cart, which writes an empty cart row, which then overwrites the guest cart during the merge.
The official Store API session documentation describes the merge order, but it assumes nothing touches the cart between init and wp_loaded. The cart block does touch it, indirectly, when it bootstraps its REST schema right after the redirect.
The other contributor I see often is a security plugin that strips the wp_woocommerce_session_ cookie on login as a session-fixation mitigation. If yours does that, the migration cannot find the guest cart at all and the merge silently writes an empty array.
The Fix
Three things to land. Skip any one and the bug returns under load.
First, persist the guest cart key explicitly so the merge has something to read from. Hook into wp_login and pass the guest session key through a short-lived transient:
add_action( 'wp_login', function ( $user_login, $user ) {
if ( ! function_exists( 'WC' ) || ! WC()->session ) {
return;
}
$guest_key = WC()->session->get_customer_id();
if ( empty( $guest_key ) ) {
return;
}
set_transient(
'wc_guest_cart_handoff_' . $user->ID,
$guest_key,
MINUTE_IN_SECONDS * 5
);
}, 5, 2 );
add_action( 'woocommerce_load_cart_from_session', function () {
$user_id = get_current_user_id();
if ( ! $user_id ) {
return;
}
$guest_key = get_transient( 'wc_guest_cart_handoff_' . $user_id );
if ( ! $guest_key ) {
return;
}
$handler = WC()->session;
$guest_data = $handler->get_session( $guest_key, [] );
if ( ! empty( $guest_data['cart'] ) ) {
$current = WC()->cart->get_cart();
$merged = array_merge(
$current,
(array) maybe_unserialize( $guest_data['cart'] )
);
WC()->cart->set_cart_contents( $merged );
WC()->cart->set_session();
}
delete_transient( 'wc_guest_cart_handoff_' . $user_id );
}, 1 );
The transient survives the redirect, the second hook fires before the cart block initialises its REST endpoint, and the merge runs against real guest data instead of an already-emptied user row.
Second, stop any mini-cart counter from touching WC()->cart before wp_loaded. If your theme has something like add_action( 'init', 'theme_count_cart_items' ), move it to wp_loaded with priority 20. That gives the session handler a clean window to finish the migration without your code racing it.
Third, audit security plugins for "regenerate WooCommerce session on login" toggles. Wordfence and iThemes Security both ship that option. Turn it off. The merge handler above already keys on the user ID, so the fixation risk is covered, and the plugin's cookie reset undoes the handoff.
Push the change, sign in mid-checkout as a guest, and confirm the Store API response now includes the items. If it does not, dump WC()->session->get_session_data() inside the woocommerce_load_cart_from_session hook and check whether the guest key actually resolved. Nine times out of ten the transient was never written because something redirected before wp_login ran.
For a related session bug that looks identical but has a different root cause, read WooCommerce mini cart not updating after AJAX.
If your WooCommerce store is losing carts at the login step and you need the migration audited end to end, that is what I do. See my services.
Losing carts on login? Get the migration audited.