Building SnackStation started with a simple idea: use WooCommerce for the commerce backend and React for the customer-facing experience. The architecture made sense. WooCommerce would handle products, orders, and payments while React handled the storefront.
Then I got to the cart.
The Problem
WooCommerce works well when its frontend and backend live together. But once the frontend is completely separated from WordPress, some of the assumptions WooCommerce makes around sessions and cart state become a problem.
I spent weeks troubleshooting the cart and different WooCommerce add-ons. Getting products into the cart was one thing; keeping that cart consistent across requests from a separate React application was another.
Eventually, it became clear that I wasn't dealing with just a small bug. I was trying to make a traditional WooCommerce cart behave like an API-driven cart.
Enter CoCart
That's where CoCart came in.
CoCart gave me an API-first way of working with the WooCommerce cart, which was much closer to what my React frontend needed.
But adding CoCart wasn't the end of the problem. I still had to deal with product add-ons, coupons, checkout, delivery fees, payment gateways, and our own frontend requirements. So instead of relying entirely on the default cart flow, I built a custom REST API around WooCommerce.
$namespace = 'ss/v1';
register_rest_route($namespace, '/cart', [
'methods' => 'GET',
'callback' => [$this, 'get_cart']
]);
register_rest_route($namespace, '/cart/add', [
'methods' => 'POST',
'callback' => [$this, 'add_to_cart']
]);
This gave the React application a consistent API for interacting with the cart while WooCommerce remained responsible for the actual cart logic.
The Session Problem
This was where things got interesting. REST requests don't necessarily go through WooCommerce's normal frontend lifecycle, so I had to explicitly make sure the cart and session were loaded.
I used and made sure a customer session existed before interacting with the cart.
wc_load_cart()
private function ensure_cart() {
if (function_exists('wc_load_cart') && is_null(WC()->cart)) {
wc_load_cart();
}
if (WC()->session && !WC()->session->has_session()) {
WC()->session->set_customer_session_cookie(true);
}
}
That took care of loading the cart, but we also had to explicitly persist changes using after cart operations.
WC()->cart->set_session()
For example, after adding an item:
$key = WC()->cart->add_to_cart(
$product_id,
$quantity,
0,
[],
$cart_item_data
);
WC()->cart->set_session();
The same pattern was used when updating quantities, removing items, clearing the cart, and applying coupons.
Keeping Add-ons Intact
SnackStation also had product add-ons, so the cart couldn't just store a product ID and quantity.
We passed the selected add-ons into the cart item data and then extracted them again when returning the cart to React.
That meant the frontend could receive a clean structure alongside each cart item instead of having to understand WooCommerce's internal cart object.
selectedAddons
Connecting CoCart to Checkout
Getting the cart working was one thing. Making checkout use the same cart session was another.
My checkout endpoint checked for my own header and fell back to CoCart's x-ss-cart-key when necessary.
x-cocart-session
$cart_key = $request->get_header('x-ss-cart-key');
if (!$cart_key) {
$cart_key = $request->get_header('x-cocart-session');
}
if ($cart_key !== '' && WC()->session) {
WC()->session->set_customer_id($cart_key);
}
I then loaded WooCommerce's cart from that session before creating the order. This was the bridge that allowed our custom checkout flow to work with the cart session being managed through CoCart.
What I Learned
The biggest lesson from SnackStation wasn't simply "use CoCart."
It was understanding that headless WooCommerce isn't just about putting React in front of WordPress. WooCommerce has its own assumptions around sessions, carts, and its frontend lifecycle.
CoCart gave me the API-first cart layer I needed, but I still had to deliberately connect React, CoCart, WooCommerce sessions, and my custom API.
It took a frustrating few weeks to get there.
But once I stopped trying to force the traditional WooCommerce cart into a headless architecture and built around the way the systems actually worked, everything started making a lot more sense.
Lanre Malumi
https://lanremalumi.laravel.cloud
Most days, Lanre is somewhere between a Figma file and a Laravel project, trying to make both behave. Accessibility isn't an afterthought for him. "Works for everyone" is the actual bar, not a nice-to-have. When he's not working on something, he's probably watching a movie, overthinking a scene, or adding yet another title to his watchlist.
You might also like...
25 Aug, 2026
Laravel 13.26 Made Queue Rerouting Much Cleaner
I recently wrote about some of the queue improvements in Laravel 13.25, especially the ability to pause all queues at once. Then Laravel 13.26 showed...
15 Aug, 2026
When WordPress Stopped Making Sense
For a long time, WordPress was more than enough for what I needed. I had used it for regular websites, ecommerce projects, content management and clie...
13 Aug, 2026
A Few Things I Noticed in Laravel 13.25
Laravel 13.25 landed with some updates and fixes. Rather than go through everything in the release, three changes stood out to me, particularly becaus...
Comments
0 approved comments
No approved comments yet.