TL;DR
- Popular add-on plugins (WooCommerce Product Add-Ons, TM Extra Product Options, YITH) let you add fields to a product, but those fields are display only — they don’t draw down stock.
- WooCommerce’s own answer, variations, can track stock per combination — but you have to create every combination by hand, which explodes into hundreds of rows (the “196 / 640 variations” trap).
- CraftForms tracks stock per option combination instead: a Dynamic SKU Expression builds a SKU from the customer’s selections, and a Custom Stock Management table maps each resolved SKU to a quantity.
- On add-to-cart, the requested quantity is checked against that SKU’s stock on the server and blocked if it would oversell — no variation matrix required.
- The resolved values are exposed as
wc_skuandwc_stock, so you can reuse them in your pricing and on-page messaging formulas.
The running example in this guide is a Custom Dining Table configurator built with CraftForms. The customer picks a wood type (
oak,walnut,ash), a size (150x90,180x100,200x110), and a leg style (black-metal,brass,oak-wood,hairpin), optionally adds engraving, and sets a quantity. Those three option fields —wood_type,size,legs— are the axes we’ll track stock against.
The features in this guide are part of CraftForms Pro and require the WooCommerce integration.
The problem: add-ons take orders, but they don’t count stock
If you sell configurable products, you’ve almost certainly reached for an “extra product options” or “product add-ons” plugin. They’re great at one thing: putting extra fields — a colour, a size, an engraving, a finish — onto the product page so customers can configure what they want.
What they’re not built to do is track inventory for those options. The add-on field is a label that travels with the order; it isn’t connected to any stock count. So if you have 8 black handles and 3 brass ones, nothing stops a customer ordering 10 brass. You find out when you go to fulfil the order.
This is one of the most repeated complaints in the WooCommerce community — phrased as “alternatives to YITH product options that actually track inventory,” “product options with stock control,” and “variable products with inventory per attribute.” People want the rich configuration of an add-on plugin and real stock control. Normally you can’t have both.
Why variations aren’t the answer either
WooCommerce does track stock per combination — through variations. The catch is that a variation is a finished, pre-built row: you have to create every combination in advance, set its stock, and maintain it.
That’s fine for a T-shirt (3 sizes × 2 colours = 6 rows). It collapses the moment you have more than a couple of option axes:
- Our dining table is modest by configurator standards — 3 woods × 3 sizes × 4 leg styles = 36 variations — and you’d still have to build and stock-manage all 36 by hand, before you even add engraving.
- Push the axes a little — 4 sizes × 7 colours × 7 finishes = 196 variations — and you hit exactly the “196 variations, need an upgrade option that doesn’t create new inventory” scenario people describe.
- Add one more axis and you’re into the “640 manual options for one product” territory.
Every one of those rows is created and stock-managed by hand. Most shops give up and either drop options or stop tracking stock for them — which puts you right back at the first problem.
The CraftForms approach: a SKU built from the selection, and one stock table
CraftForms takes the middle path. You don’t pre-build every combination. Instead you describe, with one expression, how to derive a SKU from whatever the customer picked, and you keep a single table of how much stock each SKU has. Stock is then tracked per option combination — without a variation matrix.
Two building blocks do the work, and both live in the same place: open the product, go to its CraftForms product settings, and expand the Inventory section (it’s open by default).
1. Dynamic SKU Expression
A small expression that returns a SKU string built from the form’s field values. For our dining table:
'TBL-' ~ wood_type ~ '-' ~ size ~ '-' ~ legs
If a customer selects walnut, the 180×100 size, and brass legs, this resolves to TBL-walnut-180x100-brass. The expression can reference any field or smart variable on the form (wood_type, size, legs here), so the SKU can encode as many option axes as you need.
A few details worth knowing:
- It’s optional. Leave it blank and CraftForms falls back to the product’s own WooCommerce SKU.
- The resolved value is exposed to your other formulas as
wc_sku, so you can reuse it in pricing or in a message on the page. - It’s evaluated on the server at submit from sanitised field data — the browser never gets to declare the SKU.
2. Enable Custom Stock Management + the Stock Table
Flip on Enable Custom Stock Management and a Stock Table appears. It’s a simple two-column table:
| SKU | Stock Qty |
|---|---|
TBL-oak-150x90-black-metal | 12 |
TBL-walnut-180x100-brass | 3 |
TBL-ash-200x110-hairpin | 7 |
Each row maps a resolved dynamic SKU to the quantity you have on hand — e.g. only 3 walnut / 180×100 / brass tables are buildable because you have 3 sets of brass legs left. Decimal quantities are supported (useful for length- or area-based stock), and the table has Import CSV / Export CSV buttons — so you can edit your whole stock list in a spreadsheet and paste it back, instead of clicking through rows.
When this is on, the available quantity is also exposed as wc_stock, so you can drive on-page messaging or pricing from it (more on that below).
Wiring it to the dining table form
On the Custom Dining Table product, the Inventory panel ends up looking like this:
- Quantity Variable:
qty— the number input the form shares with the Add-to-Cart button. - Dynamic SKU Expression:
'TBL-' ~ wood_type ~ '-' ~ size ~ '-' ~ legs— references the three option fields by name. - Enable Custom Stock Management: on.
- Stock Table: one row per combination you actually stock, e.g.
TBL-oak-150x90-black-metal → 12.
The field names are what matter — they have to match the form. In this configurator the wood and leg pickers are rich image-card radio options and size is a select, but to the SKU expression they’re just wood_type, size, and legs resolving to clean, SKU-safe values (oak, 180x100, brass). That’s why the option values are kept short and slug-like even though the on-card labels read “Oak”, “Walnut”, etc.
How oversell prevention actually works
Here’s the flow when a customer hits add-to-cart, all of it server-side and authoritative:
- CraftForms evaluates your Dynamic SKU Expression against the submitted selections to get the resolved SKU (e.g.
TBL-walnut-180x100-brass). - It looks that SKU up in your Stock Table.
- It reads the requested quantity from the field you nominated as the Quantity Variable — on this form that’s
qty(if you don’t set one, it assumes 1). - If the requested quantity exceeds the available stock for that SKU, the add-to-cart is blocked with a clear message:
Only 3 item(s) available for this configuration.
Because this runs on the server before any cart operation, a tampered browser request can’t sneak an oversell through.
One important rule to design around: a SKU that isn’t in the Stock Table is not restricted. The table is an allow-and-limit list, not a deny-by-default gate. So if you want every combination capped, make sure every resolvable SKU has a row. Combinations you deliberately leave out of the table will sell without a stock check — which is convenient for “made to order, unlimited” options, but a gotcha if you assumed missing = blocked.
Putting wc_stock to work (beyond just blocking)
Because the resolved stock is available as wc_stock in your other formulas, you don’t have to limit yourself to the hard block. A few patterns shops like:
- Low-stock nudges — show “Only a few of these brass-leg tables left” when
wc_stockdrops below a threshold, using a smart variable feeding an on-page message. - Per-configuration pricing tied to availability — e.g. a small surcharge on a scarce leg style or wood.
- Surfacing the SKU — echo
wc_sku(TBL-walnut-180x100-brass) into the order/confirmation so fulfilment sees exactly which combination was bought.
The point is that the same values powering the oversell check are first-class inputs to everything else CraftForms calculates.
When native variations are still the right call
Be fair to variations — they’re the right tool when:
- You have a small, fixed set of combinations.
- Each combination needs its own image, SKU, weight, and shipping treatment that WooCommerce manages natively.
- You want those combinations to appear in WooCommerce reports as distinct products.
CraftForms’ Dynamic SKU + Custom Stock Management is the answer when the combination count is large, when you don’t want to pre-build a matrix, or when you want rich option configuration (with live pricing) and real per-option stock control on the same product.











