Ecommerce platforms

WooCommerce variable products: the parent is a container, the variation is the product

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.

Three objects, and only one of them is for sale

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.

The parent is never added to a cart. Anything that only exists on the parent, whether a SKU, a stock count or a price, is either ignored at checkout or silently shared across every variation.

One SKU per variation, and the parent's SKU is a label

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.

Stock belongs on the variation, not the parent

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 CSV shape that creates the whole structure

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,,1

Parent 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.

Why the parent says out of stock when the variations have stock

  • Stock is managed on the parent with a quantity of zero, and the variations do not manage their own. Every variation inherits the zero.
  • A variation has stock but no price. A variation without a regular price is not purchasable, and a variable product with no purchasable variation shows as unavailable. A bulk price edit that skipped a row is the usual cause.
  • The variations are disabled. Published is 0 or -1 on the variation row, or the Enabled box is unticked, and a disabled variation counts for nothing.
  • The parent's stock status is stale after a direct database write, a sync tool writing meta rows, or an import that touched variations without the parent being saved afterwards. Re-saving the product, or regenerating the product lookup tables, fixes it.

Why a variation cannot be selected

  • The attribute is not marked Used for variations on the parent. It shows on the Additional information tab and nowhere else. The top cause on hand-built products.
  • The variation's value does not match a term. The dropdown offers the parent's terms and the variation stores a term slug, so Navy Blue on the parent and Navy-Blue on the variation never meet.
  • The product has more variations than the AJAX threshold, thirty by default, so the dropdowns list every term and only report a mismatch after selection. Not a fault, but it hides combinations that were never created.

Why does my WooCommerce variable product show out of stock when the variations are in stock?

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.

Share this guideLinkedInXWhatsAppFacebook
All guides