A variable product that shows out of stock while its sizes have units on the shelf is a modelling mistake, not a bug. How attributes, terms and variations relate, where the SKU and the stock count belong, and the CSV shape that builds it correctly.
By Rehan Idrisi · · 7 min read
Part of: WooCommerce
A variable product is the first place a WooCommerce catalog goes wrong in a way the admin screen hides. The product page loads, the dropdowns are there, and then the customer picks Medium and reads that the product is out of stock, while the stock report says twelve units on the shelf. We have untangled enough of these on apparel and footwear catalogs to know the cause is almost never a bug. The product was built in a shape WooCommerce did not expect.
Import mechanics are covered in our import guide. This one stays on the variable product itself: where the SKU and the stock count belong, the CSV shape that builds it in one pass, and the mistakes behind the symptom above.
An attribute is a named axis: Size, or Colour. A term is one value on that axis: Medium, or Navy. A global attribute lives as a taxonomy in WordPress, shared across the store. A custom attribute is typed into one product and exists nowhere else. Both work for variations. Use global attributes for anything that repeats across products, because layered navigation and attribute filters read taxonomies, not free text.
A variation is a separate record. Under the hood it is its own post, of type product_variation, with the variable product as its parent, and it carries its own SKU, price, stock, weight, image and tax class. The variable product is the container. It holds the title, description, gallery and attribute list, and it declares which attributes are used for variations. Nobody ever buys the container. Every order line references a variation.
Each variation gets its own SKU because each variation is a distinct physical item to pick and count. A shirt in six sizes is six SKUs. The parent can carry a SKU too, useful as a style code that groups the family in reports, but it should never be a code your warehouse expects to find on a shelf.
Blank variation SKUs are where this bites. WooCommerce falls back to the parent's SKU when it displays a variation with none of its own, so the packing slip and the order screen show the same code for a Small and an Extra Large. The size is still on the order line as an attribute, but every downstream system that keys on SKU, from a stock sync to a marketplace feed, sees one product where there are six. The same fallback applies to weight, dimensions and the image, which misleads a shipping calculation the moment sizes differ in weight.
Our SKU guide covers the pattern. The short version is a style code plus a fixed-width variant suffix, KRT-0107 on the parent and KRT-0107-IND-M on the variation. Uniqueness is enforced across the whole catalog, variations included.
WooCommerce lets you manage stock at either level. Tick Manage stock on the parent and type a quantity, and every variation that does not manage its own stock draws from that one pool. Sell a Medium and the number available for Large drops too. That is right for a made-to-order product where the variation is a label on one shared item. For anything held as physical units per size it is wrong. A Large sells out because Mediums did.
Manage stock on each variation instead. Each one gets its own quantity, low-stock threshold and backorder setting. The parent's stock status is then computed from its children: in stock if any enabled variation is purchasable and has stock, out of stock otherwise. WooCommerce recomputes that on save. It does not recompute it when a row is written straight into the database, which is the first place to look when the numbers and the badge disagree.
Tax class is per variation as well, and in India that is not academic. Apparel carries a different GST rate above a price threshold, so a plus size priced higher than the rest of the range can sit in a different tax class from its siblings. The variation's tax class defaults to Same as parent and is overridden on the one that needs it.
The importer expects a parent row typed variable, then one row per variation typed variation, each pointing back through the Parent column. The parent lists every value for every attribute. Each variation names one value per attribute and carries its own SKU, price and stock. The parent's price and stock cells stay empty.
Type,SKU,Name,Parent,Published,Regular price,Stock,In stock?,Attribute 1 name,Attribute 1 value(s),Attribute 1 visible,Attribute 1 global,Attribute 2 name,Attribute 2 value(s),Attribute 2 visible,Attribute 2 global
variable,KRT-0107,Cotton Kurta,,1,,,1,Size,"S, M, L, XL",1,1,Colour,"Indigo, Rust",1,1
variation,KRT-0107-IND-S,,KRT-0107,1,1299,8,1,Size,S,,1,Colour,Indigo,,1
variation,KRT-0107-IND-M,,KRT-0107,1,1299,0,0,Size,M,,1,Colour,Indigo,,1
variation,KRT-0107-RST-L,,KRT-0107,1,1399,5,1,Size,L,,1,Colour,Rust,,1Parent holds the parent's SKU, as here, or id:123 when adding variations to a product that already exists. Keep the parent row above its variations when the parent is new. The importer marks an attribute as used for variations the moment a variation row uses it, so this shape produces working dropdowns with no visit to the product screen afterwards. Prices are plain numbers in the store currency, 1299 for INR 1,299, with no symbol and no thousands separator.
A few things are easy to get wrong in the sheet. The attribute name on a variation row has to match the parent's spelling exactly, because Colour and Color are two different attributes. A variation cell holding a list, S and M together, is a mistake, since a variation has one value per axis. A parent row with no variation rows behind it imports as an empty shell. Our WooCommerce CSV Validator checks all three, plus Parent references that point nowhere and duplicate SKUs, before the file goes near a live store.
Because the parent's stock status is derived from its variations and something broke the link: stock managed on the parent instead of each variation, a variation with stock but no price, disabled variations, or a stale status after a direct database write or import. Enable stock management per variation, give every variation a price, then re-save the product.
Check a WooCommerce product CSV before you import it: missing columns, duplicate SKUs, orphaned variations and malformed prices, by row.
Generate consistent SKU codes in bulk from a custom pattern, with variant permutations, sequential numbering and collision checking. Free, no sign-up.
Check a product CSV for missing columns, broken variant handles, duplicate SKUs, invalid barcodes and bad prices before you import it.