I’m working on a custom AJAX-based Add to Cart implementation in WooCommerce. It works perfectly for simple products: the product is added via AJAX, a cart counter updates, and a slideout cart opens.
Here’s the simplified version of the code I’m using:
jQuery(function ($) {
$('body').on('submit', 'form.cart', function (e) {
e.preventDefault();
const form = $(this);
const button = form.find('button[type="submit"]');
const svg = button.find('#cartsvg');
const loader = button.find('.cartloader');
const formData = new FormData(form[0]);
if (!formData.has('add-to-cart')) {
const addToCartVal = form.find('[name=add-to-cart]').val();
formData.append('add-to-cart', addToCartVal);
}
const ajaxUrl = wc_add_to_cart_params.wc_ajax_url.toString().replace('%%endpoint%%', 'add_to_cart');
if (typeof wc_add_to_cart_params !== 'undefined' && wc_add_to_cart_params.wc_ajax_nonce) {
formData.append('_wpnonce', wc_add_to_cart_params.wc_ajax_nonce);
}
button.prop('disabled', true).addClass('loading');
svg.addClass('loading-cart-icon');
loader.addClass('visible-loader');
$.ajax({
url: ajaxUrl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function (response) {
if (response && response.fragments) {
$.each(response.fragments, function (key, value) {
$(key).replaceWith(value);
});
}
$('#cart-items-count').text((parseInt($('#cart-items-count').text()) || 0) + 1);
const productTitle = form.closest('.product, .single-product').find('.product_title').text().trim();
$('#slideout-cart-content').text(productTitle);
$('#ajax-cart-slideout, #ajax-slideout-overlay').removeClass('translate-x-full hidden').addClass('block');
button.prop('disabled', false).removeClass('loading');
svg.removeClass('loading-cart-icon');
loader.removeClass('visible-loader');
},
error: function (xhr, status, error) {
console.log('AJAX Error:', status, error);
button.prop('disabled', false).removeClass('loading');
svg.removeClass('loading-cart-icon');
loader.removeClass('visible-loader');
}
});
});
function closeSlideout() {
$('#ajax-cart-slideout').addClass('translate-x-full');
$('#ajax-slideout-overlay').addClass('hidden');
}
$('#close-slideout').on('click', closeSlideout);
$('#ajax-slideout-overlay').on('click', closeSlideout);
});
Observed Behavior:
Product (variable or simple) gets added to cart via AJAX.
Cart updates correctly.
Product variant (e.g., “Edition: Book”) is displayed correctly in mini cart and cart page.
Issue:
At checkout, WooCommerce throws this error:
“Please choose product options by visiting the product page…”
Even though attribute_pa_edition, add-to-cart, product_id, and variation_id are all present in the FormData payload (and logged as such), WooCommerce seems to treat the cart item as incomplete or invalid.
FormData logged before submission (example):
attribute_pa_edition: buch
add-to-cart: 68808
product_id: 68808
variation_id: 68809
Verified correct nonce and endpoint.
WooCommerce scripts wc-add-to-cart-variation.js and wc-add-to-cart.js are loaded.
formData logs expected keys and values.
When using the native WooCommerce form (without AJAX), everything works correctly.
What causes WooCommerce to reject a variable product at checkout after successful AJAX add to cart, despite correct variation data?
Is there something missing (e.g., in session data or cart item validation) that the native submit process handles, but my AJAX call does not?