Bundling Products in WooCommerce
Most "bundle" requirements are a pack-size variation or a grouped product, and cost nothing. Only a genuine set price across separate items justifies a bundling extension.
A bundle is not one of WooCommerce's product types, but most of what people want from one already ships in core. Before comparing plugins, decide which of three things you are actually selling: a fixed pack at a fixed price, a set of existing products sold together for less than their separate total, or a set the customer assembles from choices you permit. Core covers the first properly, the second partially, the third not at all — and the difference is usually three minutes of configuration rather than a purchase.
What grouped and variable products already give you
WooCommerce ships four product types, and two of them are mistaken for bundles daily.
- Grouped product. A parent with no price and no add-to-cart button of its own, listing child products with individual quantity boxes. Each child goes into the cart as its own line item at its own price. It is a catalogue device, not a bundle: no set price, no discount, no all-or-nothing purchase.
- Variable product. One product, one line item, price and stock varying by attribute combination. If your "bundle" is really a pack size, this is the answer — a 6-pack and a 12-pack are two variations of one product, not two bundles. Build it the way product variations are meant to be built and you get one SKU, one stock number and a checkout with nothing extra to go wrong.
That last case is the one people over-engineer. If the components are never sold separately, they are not components. They are a variation.
Where core genuinely stops
Three requirements have no native equivalent:
- Pricing the set differently from the sum of its parts, so buying together is cheaper.
- Keeping the set as a single cart line while still decrementing each component's own stock.
- Letting the customer pick one option per slot inside the set.
| What you are selling | Native option | Extension needed |
|---|---|---|
| Pack of 6 or 12, never sold loose | Variable product | No |
| "Everything in this range" on one page | Grouped product | No |
| Three named products, one discounted price, one line item | — | Yes |
| Customer picks one shirt, one tie, one belt | — | Yes, and closer to a configurator |
| Product plus an engraving fee | — | Add-ons, not bundles |
What a bundling plugin actually adds
The capabilities that separate a real bundle extension from a grouped product are worth naming, because they are what you should be shopping for:
- Fixed versus per-item pricing. Either the bundle has one price, or each component keeps its price and the bundle applies a discount. The second is what survives tax and reporting better.
- Optional and quantity-controlled items. Minimum and maximum quantities per component, and items the customer can decline.
- Component stock synchronisation. The bundle's availability derived from its components, checked at add-to-cart, not only at page render.
- Assembled versus unassembled shipping. Whether the bundle ships as one package with its own weight and dimensions, or as its constituent items with theirs. This single setting decides whether your shipping rates are right.
WooCommerce's own Product Bundles extension is the reference implementation of this behaviour, and its feature list is a reasonable checklist even if you buy elsewhere.
Judging a free bundle plugin
Free bundling plugins exist and some are entirely adequate. What free tiers usually cut is predictable, so test these four things on staging before committing a catalogue to one:
- Can a variable product sit inside the bundle, with its variation selector working in the bundle form?
- Does buying the bundle decrement each component's stock, or only the parent's?
- Does the cart show one line item or several, and is that what your invoices and picking lists need?
- Do coupons behave? A percentage coupon has to apply to something, and bundles are where discount logic gets confused — the same territory as coupons that will not apply.
Where bundles break after launch
Stock is the usual failure, and the second is shipping. If components have their own weights and the bundle also has one, work out which is being sent to the carrier; doubling is common. Tax is third: a bundle containing items in different tax classes has to allocate its price across them, and a fixed bundle price makes that allocation somebody's decision. Reporting is fourth — decide early whether a sale counts against the bundle or its components, because changing your mind later rewrites your history.
If you are listing bundles on custom pages rather than the shop archive, the product shortcodes still work on bundle products, but the add-to-cart button in a loop cannot show a bundle's configuration form. Bundles need their own product page.
Choose the smallest thing that works
Pack size means a variation. "Buy any of these from one page" means a grouped product. "These specific items, one price, one line" means a bundle extension. "Pick one from each of these lists" is a product configurator and has a different data model. "This product, plus a personalised message" is product add-ons. Getting that classification right before installing anything saves more time than any plugin comparison will.
Frequently asked
- Partly. A grouped product lists several products on one page with individual quantity boxes, and a variable product handles pack sizes as variations. Neither can price the set differently from the sum of its parts, which is the one thing a real bundle does.
- Yes, several exist in the plugin repository. The features usually held back are per-item pricing, optional and conditional items, component-level stock synchronisation and support for variable products inside a bundle. Check those four before assuming a free plugin fits.
- Because the bundle parent has its own stock field, and unless the plugin syncs it to each component, the parent stays in stock after a component sells out. Confirm the plugin reads component stock at add-to-cart time, not only when the page is rendered.