Sometimes “add to cart” needs a gatekeeper. Maybe a product can’t be sold below a certain quantity, maybe it’s out of stock in a way WooCommerce’s own stock field doesn’t catch, or maybe two products in your catalog should never sit in the same order. The woocommerce_add_to_cart_validation filter is the hook that lets you stop the add-to-cart action before it happens, with a clear message telling the customer why.
The Code
Here’s a snippet that blocks add-to-cart in two common scenarios: enforcing a minimum order quantity, and blocking a product marked as unavailable through a custom field.
add_filter( 'woocommerce_add_to_cart_validation', 'fixelar_validate_add_to_cart', 10, 3 );
function fixelar_validate_add_to_cart( $passed, $product_id, $quantity ) {
// Rule 1: enforce a minimum quantity of 3 units for this product
$minimum_quantity_product_id = 1234;
if ( $product_id === $minimum_quantity_product_id && $quantity < 3 ) {
wc_add_notice(
__( 'This product requires a minimum order of 3 units.', 'woocommerce' ),
'error'
);
$passed = false;
}
// Rule 2: block a product flagged as unavailable via custom field
$is_unavailable = get_post_meta( $product_id, '_fixelar_unavailable', true );
if ( 'yes' === $is_unavailable ) {
wc_add_notice(
__( 'This product is temporarily unavailable for purchase.', 'woocommerce' ),
'error'
);
$passed = false;
}
return $passed;
}
Where to Put This Code
This goes in your child theme’s functions.php, or in a snippets plugin like Code Snippets. As always, never place custom code in a parent theme’s functions.php, since a theme update will overwrite it without warning.
One thing worth flagging before you deploy this: wc_add_notice() writes to the WooCommerce notices session on every request where validation fails, including AJAX add-to-cart requests. If your theme doesn’t refresh notices correctly after an AJAX add-to-cart, the error message can get swallowed and the customer just sees the button do nothing. Test this on both a standard product page and any AJAX “Add to cart” buttons on shop or category pages before you consider it done.
How the Filter Works, Line by Line
add_filter( 'woocommerce_add_to_cart_validation', 'fixelar_validate_add_to_cart', 10, 3 ); hooks your function in with priority 10 and tells WordPress to pass 3 arguments. WooCommerce actually sends more arguments than that ($variation_id, $variations, $cart_item_data), but you only need to declare the ones you’re going to use.
$passed is a boolean coming in as true. Your job is to flip it to false when the item shouldn’t be added. Whatever your function returns is what WooCommerce uses to decide whether to proceed.
$product_id and $quantity are exactly what they sound like: the product being added and the quantity requested in the add-to-cart form.
wc_add_notice( $message, 'error' ) is what actually shows the customer why the action was blocked. Setting $passed = false without calling wc_add_notice() first stops the add-to-cart silently, which just looks like a broken button to the customer. Always pair the two.
get_post_meta( $product_id, '_fixelar_unavailable', true ) reads a custom field on the product. This is the pattern to reuse any time your validation logic depends on something WooCommerce’s default stock or status fields don’t cover.
Real Use Cases
Minimum order quantities for wholesale or bulk items. Some products only make sense to sell in case quantities. This filter blocks single-unit purchases without needing a separate wholesale plugin.
Products that go out of stock faster than inventory updates. If you’re syncing stock from an external system on a delay, a custom “unavailable” flag checked at add-to-cart time is a safety net between sync intervals.
Preventing incompatible product combinations. If a store sells add-ons that only work with specific base products, this filter can check the current cart contents with WC()->cart->get_cart() and block the addition if the required base product isn’t present yet.
Age-restricted or licensed products. Some stores need to confirm a customer meets a condition, like accepting a terms checkbox, before a specific product can be added. This filter is the enforcement point, even if the checkbox itself lives elsewhere in the page.
Common Issues
The button does nothing and no error shows. This almost always means $passed was set to false without a matching wc_add_notice() call, or the notice isn’t being rendered because of an AJAX refresh issue in the theme.
Validation works on the product page but not from the shop page’s quick add-to-cart. AJAX add-to-cart buttons run through a different request path than the single product page form. Test both separately, since a fix that works on one doesn’t guarantee it works on the other.
The rule doesn’t fire for variable products. $product_id on a variable product refers to the parent product, not the specific variation the customer selected. If your rule needs to target a specific variation, check $variation_id instead, which you’ll need to add as a fourth parameter in your function signature and in the add_filter() priority/argument count.
Multiple plugins hook into this filter and the wrong one wins. Since this filter can be hooked by more than one plugin or your theme, priority order matters. If two rules conflict, adjust the priority number in add_filter() to control which one runs, and in what order.
If you’d rather have this tested against your specific catalog and edge cases before it goes live, this is exactly the kind of custom logic Fixelar builds and QA’s as part of our WooCommerce custom development services. We validate against a clone of your store first, so a rule that works in testing doesn’t break checkout for real customers.