Comparison/9 min read/May 26, 2026

Free code snippet vs plugin for WooCommerce purchase limits

“Why pay for a plugin when I can just paste a snippet?” is a completely fair question, and for some stores the snippet is genuinely the right answer. This post gives you the snippet to copy, an honest accounting of what it can and can’t do, and a clear rule for when the snippet quietly becomes the more expensive option.

Short version. A snippet is the right call for one product, one launch, that you’ll personally babysit. A plugin wins the moment you have multiple products, variations, guest + logged-in matching, an admin bypass, or you’d rather not maintain WooCommerce code through core updates. The deciding factor is rarely the price — it’s the maintenance.

Start with the free path — really

If you’re protecting a single limited product for a single drop, you may not need to buy anything. WooCommerce gives you two free levers out of the box:

If “one per cart” is genuinely all you need, stop here — you’re done for free. The rest of this article is for when you need one per customer across every order, which neither of those does. (We unpack exactly why in the hidden flaw in WooCommerce’s max quantity setting.)

The working snippet

Here’s a real, functional snippet that enforces a lifetime per-customer limit on one product, covering both logged-in users (by ID) and guests (by billing email at checkout). Paste it into a code-snippets plugin or your child theme’s functions.php:

// 1) Block logged-in repeat buyers at add-to-cart.
add_filter( 'woocommerce_add_to_cart_validation', function( $passed, $product_id, $qty ) {
    $target = 12345; $limit = 1;
    if ( (int) $product_id !== $target || ! is_user_logged_in() ) {
        return $passed;
    }
    $bought = droplock_count_purchased( $target, get_current_user_id(), '' );
    if ( $bought + $qty > $limit ) {
        wc_add_notice( 'You have already purchased this limited product.', 'error' );
        return false;
    }
    return $passed;
}, 10, 3 );

// 2) Block guests (and double-check everyone) at checkout, by billing email.
add_action( 'woocommerce_after_checkout_validation', function( $data, $errors ) {
    $target = 12345; $limit = 1;
    $email  = sanitize_email( $data['billing_email'] ?? '' );
    $uid    = get_current_user_id();
    $bought = droplock_count_purchased( $target, $uid, $email );

    $in_cart = 0;
    foreach ( WC()->cart->get_cart() as $ci ) {
        if ( (int) $ci['product_id'] === $target ) { $in_cart += (int) $ci['quantity']; }
    }
    if ( $bought + $in_cart > $limit ) {
        $errors->add( 'limit', 'You have already reached the purchase limit for this product.' );
    }
}, 10, 2 );

// Helper: count prior purchases by user id and/or email, de-duplicated by order.
function droplock_count_purchased( $product_id, $user_id, $email ) {
    $ids = array();
    $args = array( 'status' => array('completed','processing','on-hold'), 'limit' => -1, 'return' => 'ids' );
    if ( $user_id ) { $ids = array_merge( $ids, wc_get_orders( $args + array('customer_id' => $user_id) ) ); }
    if ( $email )   { $ids = array_merge( $ids, wc_get_orders( $args + array('billing_email' => $email) ) ); }
    $ids = array_unique( $ids );

    $total = 0;
    foreach ( $ids as $oid ) {
        $order = wc_get_order( $oid );
        if ( ! $order ) { continue; }
        foreach ( $order->get_items() as $item ) {
            if ( (int) $item->get_product_id() === (int) $product_id ) {
                $total += (int) $item->get_quantity();
            }
        }
    }
    return $total;
}

This is close to what a basic plugin does internally. It’s honest, it uses the HPOS-safe wc_get_orders() API, and it covers both customer types. If your needs match its assumptions, ship it.

What the snippet is genuinely great at

What it can’t do (without becoming a project)

The snippet above is the simple version. Each gap below is fixable — but every fix is more code you write and maintain:

NeedSnippet status
Protect multiple productsCopy/edit per product, or rewrite to read per-product config
Variations share one limitNot handled — needs parent/variation roll-up logic
Different limit per productHard-coded; needs a settings store
Admin / shop-manager bypassMissing — you’ll block your own manual orders
Cart-page quantity changesNot covered — needs the update-cart hook too
Custom message & product badgeGeneric error only
Configurable counted statusesHard-coded array
A log of who got blockedNone
Performance on big storeslimit => -1 every check; needs caching
Survives WooCommerce updatesYour responsibility, forever

The real cost of a snippet isn’t the code

The snippet is free to paste. The cost shows up later: it’s the afternoon you spend adding variation support, the launch where you discover guests slipped through because you only checked logged-in users, the support ticket from a customer who got a confusing generic error, and the WooCommerce 9.x update that quietly changes a hook signature. None of that is hard individually. It’s death by a thousand small maintenance tasks — and it always lands during a launch, never on a quiet Tuesday.

Neither option stops a determined attacker. A snippet and a plugin both rely on user ID and billing email. Someone using a fresh email and a new card defeats both. The choice here is purely about coverage and maintenance for the common cases — not about fraud resistance.

A simple decision rule

At one product, the snippet is cheaper. By the third product, the plugin almost always is — once you value your time at more than zero.

What a plugin buys you

A focused plugin is the snippet’s entire feature wishlist, already built and maintained: per-product limits, variation roll-up, guest + logged-in matching merged and de-duplicated, cart and checkout validation, admin bypass, custom messaging, a badge, and a blocked-attempt log — configured per product instead of per code edit.

DropLock is that plugin

Everything in the wishlist above, as one toggle per product. The free version is open source on GitHub (GPL) and protects one product; Pro removes the one-product limit and adds custom messaging, the full log, and the roadmap. 14-day refund.

Compare Free vs Pro
Keep reading