Contents
Overview
This article is a complete, detailed tutorial on how to add products to the WooCommerce cart using AJAX and plain JavaScript. It covers multiple methods (native WooCommerce AJAX endpoints and a custom admin-ajax.php handler), how to enqueue and localize scripts, how to handle simple and variable products, how to update cart fragments / mini-cart HTML after an AJAX add, security (nonces), and common pitfalls and debugging tips. All example code blocks are provided so you can copy/paste directly into your theme or plugin.
Why use AJAX to add to cart?
- Better UX: Customers can add items without a full page reload.
- Faster flows: You can show a toast, update the mini-cart, or run custom animations.
- Flexibility: Works for custom buttons, quick-add interfaces, or single-page checkout flows.
Prerequisites
- WordPress with WooCommerce installed and active.
- Access to theme functions.php or a custom plugin to enqueue and register scripts and to add an AJAX handler.
- Basic JavaScript and PHP knowledge.
High-level approaches
- Use WooCommerces built-in wc-ajax=add_to_cart endpoint (recommended for most cases).
- Create a custom AJAX handler via admin-ajax.php that calls WC()->cart->add_to_cart() (gives full control over the response).
- Use the REST API or custom REST endpoints (useful for SPA or headless situations).
Important concepts to understand
- Cart fragments: WooCommerce refreshes mini-cart portions via fragments returned by AJAX. If you do your own AJAX add, return and replace the fragments so the cart UI updates.
- Nonce and security: Protect custom AJAX endpoints with a nonce (check_ajax_referer) to avoid CSRF.
- Product types: Simple products are trivial variable products require attributes and variation_id to be passed.
- Session cookies: The users session (cart) depends on cookies. Caching or CDN cache can break AJAX cart actions — exclude cart pages and AJAX endpoints from full-page caching.
1) Using WooCommerces wc-ajax=add_to_cart endpoint (quick method)
WooCommerce already exposes an endpoint for AJAX add-to-cart calls. The front-end script variable wc_add_to_cart_params (enqueued by WooCommerce when its add-to-cart scripts are loaded) includes a wc_ajax_url with a placeholder %%endpoint%%. Replace that placeholder with add_to_cart to get the proper URL. The endpoint will return JSON including fragments you can use to update the cart UI.
Example: Minimal JS using the WooCommerce endpoint
// Requires wc_add_to_cart_params (supplied by WooCommerce scripts) var productId = 123 // product ID var qty = 1 var wcAddToCartUrl = wc_add_to_cart_params.wc_ajax_url.toString().replace(%%endpoint%%,add_to_cart) fetch(wcAddToCartUrl, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded charset=UTF-8 }, body: new URLSearchParams({ product_id: productId, quantity: qty }) }) .then(function(response){ return response.json() }) .then(function(data){ if ( data data.fragments ) { Object.keys(data.fragments).forEach(function(selector){ var el = document.querySelector(selector) if ( el ) el.innerHTML = data.fragments[selector] }) // Dispatch an event similar to WooCommerces jQuery added_to_cart document.body.dispatchEvent(new CustomEvent(added_to_cart, { detail: { fragments: data.fragments, cart_hash: data.cart_hash } })) } else { console.error(No fragments received:, data) } }) .catch(function(err){ console.error(Add to cart failed:, err) })
Notes about this approach
- Works out of the box for simple products (no variation attributes required).
- WooCommerce will send cart fragments in the response so you can update mini-cart HTML.
- If wc_add_to_cart_params is not available (your theme didn’t enqueue the WooCommerce script), you can still create your own localized params — see the custom handler below.
2) Custom AJAX handler using admin-ajax.php (full control)
This approach creates a custom AJAX action (wp_ajax_ and wp_ajax_nopriv_) that receives POST data, validates it, calls WC()->cart->add_to_cart(), and returns JSON including refreshed fragments. Use this when you need custom validation, logging, or a different response format.
Step A — Enqueue and localize the JS
// Add this to your themes functions.php or a plugin function mytheme_enqueue_ajax_add_to_cart() { wp_register_script( my-ajax-add-to-cart, get_stylesheet_directory_uri() . /js/ajax-add-to-cart.js, array(), 1.0, true ) wp_localize_script( my-ajax-add-to-cart, my_ajax_add_to_cart_params, array( ajax_url => admin_url( admin-ajax.php ), nonce => wp_create_nonce( my_add_to_cart_nonce ), ) ) wp_enqueue_script( my-ajax-add-to-cart ) } add_action( wp_enqueue_scripts, mytheme_enqueue_ajax_add_to_cart )
Step B — The PHP AJAX handler
// Also in functions.php or your plugin function my_add_to_cart_ajax() { // Security check check_ajax_referer( my_add_to_cart_nonce, security ) product_id = isset( _POST[product_id] ) ? absint( _POST[product_id] ) : 0 quantity = isset( _POST[quantity] ) ? wc_stock_amount( wp_unslash( _POST[quantity] ) ) : 1 variation_id = isset( _POST[variation_id] ) ? absint( _POST[variation_id] ) : 0 variation = isset( _POST[variation] ) ? wp_unslash( _POST[variation] ) : array() if ( ! product_id ) { wp_send_json_error( array( message => Invalid product. ) ) } // Run WooCommerce validation hooks passed_validation = apply_filters( woocommerce_add_to_cart_validation, true, product_id, quantity, variation_id, variation ) if ( passed_validation WC()->cart->add_to_cart( product_id, quantity, variation_id, variation ) ) { // Recalculate collect fragments WC()->cart->calculate_totals() ob_start() woocommerce_mini_cart() mini_cart = ob_get_clean() fragments = array( div.widget_shopping_cart_content =>, ) data = array( fragments => fragments, cart_hash => apply_filters( woocommerce_add_to_cart_hash, WC()->cart->get_cart_hash() ), ) wp_send_json_success( data ) } wp_send_json_error( array( message => Could not add to cart. ) ) } add_action( wp_ajax_my_add_to_cart, my_add_to_cart_ajax ) add_action( wp_ajax_nopriv_my_add_to_cart, my_add_to_cart_ajax )
Step C — JavaScript to call the custom AJAX handler and update fragments
// ajax-add-to-cart.js document.addEventListener(click, function(e){ var el = e.target if ( el.matches el.matches(.my-ajax-add-to-cart) ) { e.preventDefault() var productId = el.getAttribute(data-product_id) el.dataset.productId var qty = el.getAttribute(data-quantity) el.dataset.quantity 1 var variationId = el.getAttribute(data-variation_id) el.dataset.variationId 0 var body = new URLSearchParams() body.append(action, my_add_to_cart) body.append(product_id, productId) body.append(quantity, qty) body.append(variation_id, variationId) body.append(security, my_ajax_add_to_cart_params.nonce ) fetch(my_ajax_add_to_cart_params.ajax_url, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded charset=UTF-8 }, body: body.toString() }) .then(function(res){ return res.json() }) .then(function(data){ if ( data.success data.data data.data.fragments ) { Object.keys(data.data.fragments).forEach(function(selector){ var node = document.querySelector(selector) if ( node ) node.innerHTML = data.data.fragments[selector] }) document.body.dispatchEvent(new CustomEvent(added_to_cart, { detail: { fragments: data.data.fragments, cart_hash: data.data.cart_hash } })) } else { console.error(Add to cart failed:, data) } }) .catch(function(err){ console.error(AJAX error:, err) }) } }, false)
Why return fragments?
When the cart changes, WooCommerce expects parts of the page (mini-cart, cart counters) to update. Returning the same fragments the core returns makes integration seamless: replace the fragment nodes in DOM with the returned HTML.
3) AJAX add for variable products
Variable products require sending the selected attributes and the matching variation_id. If you have a variation form (the default WooCommerce variations_form), you can serialize the form and send it with the AJAX call.
// Example: serialize a variation form and send it var form = document.querySelector(form.variations_form) if ( form ) { var fd = new FormData(form) fd.append(action,my_add_to_cart) fd.append(security, my_ajax_add_to_cart_params.nonce) // If you want to convert to URLSearchParams for fetch: var body = new URLSearchParams() for ( var pair of fd.entries() ) { body.append(pair[0], pair[1]) } fetch(my_ajax_add_to_cart_params.ajax_url, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded charset=UTF-8 }, body: body.toString() }).then(function(r){ return r.json() }).then(function(data){ // handle response (fragments etc.) }) }
4) Using the WooCommerce front-end interaction conventions
- WooCommerce performs a jQuery event added_to_cart when a product is added via its own AJAX. Emitting a similar CustomEvent helps plugins and theme parts that listen for the event.
- Update any cart counters you have by reading the returned fragments or by fetching the cart totals from the server.
5) REST API option
If you’re building an SPA or headless site, you can implement a custom REST route that calls WC()->cart->add_to_cart(). Be mindful that the cart is session-based (cookie) so your SPA must maintain the correct cookie/session. This approach is more advanced the admin-ajax / wc-ajax routes are simpler for classic themes.
6) Common pitfalls and troubleshooting
- No fragments returned: If you hit the WooCommerce endpoint but receive no fragments, check that the request is going to wc-ajax=add_to_cart. Inspect the network response (Chrome DevTools -> Network) and look for JSON with a fragments property.
- Nonces failing: If your handler uses check_ajax_referer, ensure you send the exact nonce and parameter names. Use wp_localize_script or wp_create_nonce to generate the nonce server-side and pass it to JS.
- Variable product not added: Verify you sent the correct variation_id and attributes (matching the variation). Use the variations_form serialization as shown above.
- Cached pages: Full-page cache that serves HTML without considering user session will prevent AJAX changes from showing. Exclude cart fragments endpoints and pages where add-to-cart is used from caching.
- Cookies blocked: If cookies are stripped (CORS or SameSite issues), the cart session will not persist. Ensure cookies are sent and not blocked by CORS.
- Stock / purchasable checks: Some products will fail add_to_cart because they are out of stock, not purchasable, or require extras (e.g., product addons). Handle errors gracefully.
7) Security and performance tips
- Always check nonces for custom endpoints (check_ajax_referer).
- Avoid echoing large data send only the necessary fragments to reduce payload size.
- Cache server-side HTML snippets only if they are user-agnostic (mini-cart is user-specific and should not be cached as-is).
- Debounce or disable the add-to-cart button during the request to prevent duplicate submissions.
8) Debugging checklist
- Open DevTools Network tab to inspect the POST request and JSON response.
- Check console for JavaScript errors.
- Check PHP error logs for fatal errors in the AJAX handler.
- Temporarily switch to a default theme and disable plugins to isolate conflicts.
- Test both logged-in and logged-out users (cart storage differs between them in some setups).
9) Example: Full integration (summary)
- Enqueue JS and localize with ajax_url and nonce.
- Add a custom AJAX handler that adds the product and returns fragments.
- Create a JS click listener that sends product_id, quantity (and variation data if needed).
- On success, replace fragments and dispatch an added_to_cart event.
Reference table: common POST fields
Field | Meaning |
action | The admin-ajax action name (for admin-ajax.php). E.g. my_add_to_cart. |
product_id | ID of the product to add. |
quantity | Number of items (default 1). |
variation_id | For variable products, the matched variation ID. |
variation (or attributes) | Array or serialized attributes needed to identify the variation. |
security | Nonce name/value for check_ajax_referer. |
Extra: small CSS to show loading state (optional)
/ Example loading state / .my-ajax-add-to-cart[disabled] { opacity: 0.6 cursor: not-allowed } .my-ajax-add-to-cart.loading::after { content: … margin-left: .5rem }
Wrapping up — Checklist before going live
- Verify your AJAX requests succeed for logged-in and logged-out users.
- Confirm the mini-cart and cart counters update correctly after the AJAX add.
- Test simple and variable products, including out-of-stock cases.
- Ensure caching/CDN rules do not aggressively cache AJAX endpoints and mini-cart fragments.
- Implement graceful error handling and UX messaging for failures.
If you follow the patterns above (prefer the wc-ajax endpoint for a fast solution, or use a custom admin-ajax handler for full control), you will have a robust AJAX add-to-cart implementation that supports simple and variable products and updates the user interface without page reloads.
Useful references: WooCommerce documentation and the WooCommerce codebase for core handlers (look for wc-ajax endpoints and woocommerce_mini_cart()).
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |