wc-variations-data
Read, query, and write WooCommerce product variations correctly — the WC_Product_Variable (parent) vs WC_Product_Variation (child) class split, the parent's get_variation_prices( $for_display ) cached aggregation and how to bust the wc_var_prices_<id> transient, programmatic variation creation via WC_Product_Variation + set_parent_id + set_attributes + save followed by WC_Product_Variable::sync, the inherited-vs-own variation stock model (parent flag, variation flag, derived stock_status), get_available_variations for frontend variation data, and the $for_display tax-handling parameter that breaks display logic when forgotten. Use when scaffolding plugin code that creates / queries / modifies variations or when debugging stale variation data after programmatic writes. Triggers on WC_Product_Variation, WC_Product_Variable, get_variation_prices, get_available_variations, product_variation post type, wc_var_prices, sync_variation, or programmatic variation creation context.
Best use case
wc-variations-data is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Read, query, and write WooCommerce product variations correctly — the WC_Product_Variable (parent) vs WC_Product_Variation (child) class split, the parent's get_variation_prices( $for_display ) cached aggregation and how to bust the wc_var_prices_<id> transient, programmatic variation creation via WC_Product_Variation + set_parent_id + set_attributes + save followed by WC_Product_Variable::sync, the inherited-vs-own variation stock model (parent flag, variation flag, derived stock_status), get_available_variations for frontend variation data, and the $for_display tax-handling parameter that breaks display logic when forgotten. Use when scaffolding plugin code that creates / queries / modifies variations or when debugging stale variation data after programmatic writes. Triggers on WC_Product_Variation, WC_Product_Variable, get_variation_prices, get_available_variations, product_variation post type, wc_var_prices, sync_variation, or programmatic variation creation context.
Teams using wc-variations-data should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/wc-variations-data/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How wc-variations-data Compares
| Feature / Agent | wc-variations-data | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Read, query, and write WooCommerce product variations correctly — the WC_Product_Variable (parent) vs WC_Product_Variation (child) class split, the parent's get_variation_prices( $for_display ) cached aggregation and how to bust the wc_var_prices_<id> transient, programmatic variation creation via WC_Product_Variation + set_parent_id + set_attributes + save followed by WC_Product_Variable::sync, the inherited-vs-own variation stock model (parent flag, variation flag, derived stock_status), get_available_variations for frontend variation data, and the $for_display tax-handling parameter that breaks display logic when forgotten. Use when scaffolding plugin code that creates / queries / modifies variations or when debugging stale variation data after programmatic writes. Triggers on WC_Product_Variation, WC_Product_Variable, get_variation_prices, get_available_variations, product_variation post type, wc_var_prices, sync_variation, or programmatic variation creation context.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# WooCommerce: variations data layer (CRUD + cache)
For plugin code that **reads, queries, or programmatically writes** product variations. The pricing/display side (filter chain, sale-price overrides, frontend "Variation X is selected" hooks) is sibling skill `wc-variations-pricing-filters` — this one stops at the data layer.
## Misconception this skill corrects
> "A variation is just a product. I'll `wp_insert_post( 'product_variation', ... )` and `update_post_meta` for price."
Variations work that way at the database level, but the data layer caches aggressively at the parent level. A raw post-insert leaves the parent's stored `_price` values, lookup table row, child-list transient, and `wc_var_prices_<id>` transient stale — your variation exists but the catalog shows the OLD price range, "Out of stock" stays on a re-stocked variable product, and frontend variation data may not see the new child until caches are rebuilt.
## When to use this skill
Trigger when ANY of the following is true:
- Programmatically creating, updating, or deleting variations (import scripts, sync from external system, CLI tools).
- Reading variation prices for catalog display, custom listings, or reports.
- Debugging "I added a variation programmatically and the parent's price range / stock didn't update".
- Reviewing code where variations are touched via `wp_insert_post`, `wp_update_post`, `update_post_meta`, or raw `$wpdb` writes against `product_variation` post type.
- The diff or file contains: `WC_Product_Variation`, `WC_Product_Variable`, `get_variation_prices`, `get_available_variations`, `wc_var_prices_`, `sync_variation_names`, or `'product_variation'` post type references.
## Mental model — two classes, one tree
```
Variable product (parent)
post_type = 'product'
class = WC_Product_Variable
WP post id = 100
Holds: product attributes (Color: Red, Blue; Size: S, M, L)
cached min/max prices, derived stock status
└── Variation (child)
post_type = 'product_variation'
class = WC_Product_Variation
post_parent = 100
WP post id = 101, 102, 103, ...
Holds: attribute values (attribute_pa_color = red, attribute_pa_size = m)
own price, own stock, own SKU
```
The parent stores aggregated / derived data; each child variation stores its own concrete values. Almost every "stale data" bug comes from writing to a child without telling the parent to re-aggregate.
## The price aggregation cache — `wc_var_prices_<id>`
`WC_Product_Variable::get_variation_prices( $for_display )` ([includes/class-wc-product-variable.php:99](class-wc-product-variable.php)) returns an array shaped:
```php
array(
'price' => array( 101 => '15.00', 102 => '18.00', 103 => '20.00' ), // sorted
'regular_price' => array( ... ),
'sale_price' => array( ... ),
)
```
The aggregation is backed by a transient named `wc_var_prices_<parent_id>` ([includes/data-stores/class-wc-product-variable-data-store-cpt.php](class-wc-product-variable-data-store-cpt.php)). The transient stores entries keyed by:
- A **transient version** from `WC_Cache_Helper::get_transient_version( 'product' )` (busted by `wc_delete_product_transients()`)
- A **price hash** built from current tax display settings, customer VAT-exempt status, rate table, and the active `woocommerce_variation_prices_*` callbacks — different hashes for "include tax" vs "exclude tax", different for VAT-exempt vs not, etc.
The `$for_display` parameter matters:
- `false` (default): RAW prices, before any tax adjustment. Use for storage / comparison / programmatic logic.
- `true`: prices adapted for the `woocommerce_tax_display_shop` setting (include / exclude tax). Use ONLY when rendering UI.
A common bug: read with `$for_display = true` then do business logic on the result — the value drifts depending on tax settings. Always read raw for logic, display-mode only at the render edge.
## Programmatic variation creation — the right sequence
```php
$parent_id = 100; // an existing WC_Product_Variable
$variation = new WC_Product_Variation();
$variation->set_parent_id( $parent_id );
// Attributes — keys are attribute slugs (taxonomy or custom), values are
// the chosen term slug. Both must already exist on the parent.
$variation->set_attributes( array(
'pa_color' => 'red',
'pa_size' => 'm',
) );
$variation->set_regular_price( '20.00' );
$variation->set_price( '20.00' ); // current price (= sale or regular)
$variation->set_manage_stock( true );
$variation->set_stock_quantity( 50 );
$variation->set_stock_status( 'instock' );
$variation->set_sku( 'TSHIRT-RED-M' );
$variation_id = $variation->save(); // returns the new variation post ID; clears parent product transients in WC 10.7
// CRITICAL — invalidate the parent's caches and re-aggregate.
wc_delete_product_transients( $parent_id );
WC_Product_Variable::sync( $parent_id );
```
`wc_delete_product_transients( $parent_id )` clears product-specific transients such as `wc_product_children_<id>`, `wc_var_prices_<id>`, `wc_child_has_weight_<id>`, and `wc_child_has_dimensions_<id>`. `WC_Product_Variable::sync( $parent_id )` recomputes the parent from its children: it rewrites the parent's stored `_price` meta values, updates `wc_product_meta_lookup`, refreshes derived stock status, syncs legacy attributes, then saves the parent.
Skip the sync and raw cache invalidation after direct writes and: catalog shows old price range, parent shows "out of stock" until next manual save, or frontend variation data is built from stale child/transient data.
## Querying variations
```php
// CHILDREN OF A PARENT — use $variable->get_children() (returns IDs)
$variable = wc_get_product( $parent_id );
if ( $variable instanceof WC_Product_Variable ) {
foreach ( $variable->get_children() as $variation_id ) {
$variation = wc_get_product( $variation_id );
// $variation is a WC_Product_Variation
}
}
// FRONTEND DISPLAY DATA — JSON-shaped for swatch / dropdown UIs
$available = $variable->get_available_variations(); // array of arrays
// Each entry has: variation_id, attributes, display_price, display_regular_price,
// is_in_stock, max_qty, min_qty, image, sku, weight_html, dimensions_html, etc.
```
`get_available_variations()` is what the classic variable-product template embeds inline when the variation count is below `woocommerce_ajax_variation_threshold` (default 30). Above that threshold, WC AJAX resolves one matching variation and calls `get_available_variation()` for that variation. The default array mode is heavy; if you only need objects, use `$variable->get_available_variations( 'objects' )`, or use `get_children()` and read only the properties you need.
## The variation stock inheritance model
| Setting on | What it does |
|---|---|
| Parent `manage_stock = 'yes'`, variation `manage_stock = 'no'` | Variation inherits the parent's stock quantity / backorders / stock status. Internally `WC_Product_Variation::get_manage_stock()` returns `'parent'`. |
| Variation `manage_stock = 'yes'` | Per-variation stock. The variation has its own `stock_quantity` and is managed by its own ID even if the parent also manages stock. **This is the common case for size/color inventory.** |
| Parent `manage_stock = 'no'`, variation `manage_stock = 'no'` | Variation has only `stock_status` (`instock` / `outofstock` / `onbackorder`) without a quantity counter. |
Reading the effective stock status:
```php
// Variation's own stock status (already accounts for managed-quantity rollover)
$variation->get_stock_status(); // 'instock' / 'outofstock' / 'onbackorder'
// Parent's display status — derived from children, set by sync()
$variable->get_stock_status();
```
After programmatic stock changes, `WC_Product_Variable::sync( $parent_id )` recomputes the parent's display status from the children. Without the sync the parent shows stale.
## When parent must be re-synced — checklist
Call `wc_delete_product_transients( $parent_id )` + `WC_Product_Variable::sync( $parent_id )` whenever you:
- Add a new variation
- Delete a variation
- Change a variation's regular_price / sale_price / price
- Change a variation's stock_quantity / stock_status / manage_stock
- Change a variation's enabled / disabled flag (post_status)
- Bulk update attributes that affect availability
In WC 10.7, `$variation->save()` clears product transients for the variation and its parent, but it does not replace the parent aggregation sync. For cron jobs, REST imports, CLI scripts, and any cross-request flow, collect touched parent IDs and call `WC_Product_Variable::sync()` once per parent after the batch.
## Hook checkpoints for variation data
Use these when a plugin needs to observe or extend variation data without taking over pricing filters:
| Hook | Use |
|---|---|
| `woocommerce_new_product_variation` | A variation was created through CRUD. Args: variation ID, `WC_Product_Variation`. |
| `woocommerce_update_product_variation` | A variation was updated through CRUD. Good for external index refreshes. |
| `woocommerce_new_product_variation_data` | Last chance to alter the post array before `wp_insert_post()` creates a variation. |
| `woocommerce_available_variation` | Modify the frontend variation data array returned by `get_available_variation()`. |
| `woocommerce_hide_invisible_variations` | Decide whether disabled / empty-price variations are hidden from `get_available_variations()`. |
| `woocommerce_show_variation_price` | Decide whether selected variation price HTML is included in the frontend data array. |
| `woocommerce_variable_product_sync_data` | Parent variable product has just re-synced from children. Use for dependent caches. |
## Reading variation prices for display
```php
$variable = wc_get_product( $parent_id );
// "From €X to €Y" range string — handles all the formatting + tax display
echo wp_kses_post( $variable->get_price_html() );
// Manually if you need the raw values:
$prices = $variable->get_variation_prices( true ); // for_display = true for UI
$min = current( $prices['price'] );
$max = end( $prices['price'] );
```
For non-display logic (filtering, sorting in custom listings), pass `$for_display = false` — raw prices, no tax adjustment.
## Critical rules
- **`WC_Product_Variation` is the right class** for programmatic create/update of a single variation. Don't `wp_insert_post( 'product_variation', ... )` directly.
- **`set_parent_id` + `set_attributes` + `save()`** is the minimum sequence; attributes MUST match the parent's available attribute terms.
- **After every programmatic mutation**, call `wc_delete_product_transients( $parent_id )` AND `WC_Product_Variable::sync( $parent_id )`; for bulk CRUD, sync each touched parent once after the loop. The latter is a static method on `WC_Product_Variable`, not an instance method.
- **`get_variation_prices( $for_display )` — always be explicit about `$for_display`.** Default is `false` (raw); pass `true` only at the render edge.
- **`get_children()` for cheap iteration**, `get_available_variations()` for full UI-ready data — different costs.
- **Parent stock vs variation stock are independent flags.** Most stores want variation-level (`manage_stock` on the variation, `manage_stock = no` on the parent).
- **Don't query variations via `WP_Query` / `get_posts` for hot-path code.** Use `$variable->get_children()` and `wc_get_product()` per ID — the data store handles visible-child rules and cached child lists.
- **HPOS doesn't affect variations.** Products and variations stay in `wp_posts` / `wp_postmeta` even with HPOS on (HPOS is order-table only). Variation reads / writes don't change between HPOS and legacy modes.
## Common mistakes
```php
// WRONG — raw post insert leaves parent caches stale
$variation_id = wp_insert_post( array(
'post_type' => 'product_variation',
'post_parent' => $parent_id,
'post_status' => 'publish',
) );
update_post_meta( $variation_id, '_price', '20.00' );
// Catalog shows old price range; frontend variation data may be stale.
// RIGHT
$variation = new WC_Product_Variation();
$variation->set_parent_id( $parent_id );
$variation->set_attributes( array( 'pa_color' => 'red' ) );
$variation->set_regular_price( '20.00' );
$variation->set_price( '20.00' );
$variation->save();
wc_delete_product_transients( $parent_id );
WC_Product_Variable::sync( $parent_id );
// WRONG — passing $for_display = true and then doing logic on the value
$prices = $variable->get_variation_prices( true );
if ( current( $prices['price'] ) < 10 ) { /* ... */ }
// On a tax-inclusive store, current('price') is post-tax — your < 10 threshold is wrong.
// RIGHT — read raw for logic, display-mode only at the render edge
$prices = $variable->get_variation_prices( false );
if ( (float) current( $prices['price'] ) < 10 ) { /* ... */ }
// WRONG — bulk import without sync
foreach ( $rows as $row ) {
$v = new WC_Product_Variation();
$v->set_parent_id( $row['parent_id'] );
$v->set_regular_price( $row['price'] );
$v->save();
}
// 1000 variations imported; parent lookup rows / stock aggregation can be stale.
// RIGHT — sync each parent once after the loop
$touched_parents = array();
foreach ( $rows as $row ) {
$v = new WC_Product_Variation();
$v->set_parent_id( $row['parent_id'] );
$v->set_regular_price( $row['price'] );
$v->save();
$touched_parents[ $row['parent_id'] ] = true;
}
foreach ( array_keys( $touched_parents ) as $parent_id ) {
wc_delete_product_transients( $parent_id );
WC_Product_Variable::sync( $parent_id );
}
// WRONG — assuming parent stock applies to every variation
$variable->set_manage_stock( true );
$variable->set_stock_quantity( 100 );
// Only variations with manage_stock=no inherit this. Variations with manage_stock=yes
// keep using their own stock quantity.
// WRONG — querying via WP_Query, missing data-store caches
$ids = get_posts( array(
'post_type' => 'product_variation',
'post_parent' => $parent_id,
'fields' => 'ids',
) );
// RIGHT
$ids = wc_get_product( $parent_id )->get_children();
```
## Cross-references
- Run **`wc-variations-pricing-filters`** for the price filter chain (`woocommerce_product_variation_get_price`, `woocommerce_variation_prices_price`, etc.) — when a plugin needs to mutate variation prices via filters rather than direct CRUD.
- Run **`wc-product-search-select`** when the UI needs an admin product picker — `woocommerce_json_search_products_and_variations` returns variation IDs alongside parent products.
- Run **`wp-plugin-cron`** for batch imports — cron callbacks scheduled idempotently are the right place for bulk variation operations.
## What this skill does NOT cover
- The price filter chain. Mutating variation prices via filters (without changing stored data) is a separate topic — see `wc-variations-pricing-filters`.
- The frontend variation switching UX (`found_variation` JS event, AJAX swatch / dropdown logic). That's frontend territory; this skill is data-layer.
- Variation image handling beyond `set_image_id()`. The image gallery / shared-image inheritance has its own quirks.
- Grouped products and external products — different post types, different rules.
- WC subscriptions variations (subscription products + variation pricing) — covered by the WC Subscriptions plugin, not core.
- Product attributes registration / management — orthogonal topic; variations consume already-existing attributes.
## References
- `WC_Product_Variable::get_variation_prices` and the cache: [wp-content/plugins/woocommerce/includes/class-wc-product-variable.php:99](class-wc-product-variable.php).
- `WC_Product_Variable::sync()` (static): [wp-content/plugins/woocommerce/includes/class-wc-product-variable.php](class-wc-product-variable.php).
- Variation data store, transient name `wc_var_prices_`, price hash inputs: [wp-content/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php](class-wc-product-variable-data-store-cpt.php).
- `WC_Product_Variation`: [wp-content/plugins/woocommerce/includes/class-wc-product-variation.php](class-wc-product-variation.php).
- `wc_delete_product_transients()` for cache invalidation: [wp-content/plugins/woocommerce/includes/wc-product-functions.php](wc-product-functions.php).Related Skills
wcs-data-model-switching-gifting
WooCommerce Subscriptions data model, switcher, and gifting reference for exact order type names, product type slugs, subscription meta keys, schedule/date keys, related-order relation meta, switch cart data, switch order data, switched item types/meta, proration hooks, and WCS Gifting recipient storage. Use when code reads or writes shop_subscription, subscription, variable-subscription, subscription_variation, _billing_period, _schedule_next_payment, _subscription_switch_data, _subscription_switch, subscription_switch, _switched_subscription_item_id, wcsg_gift_recipients_email, _recipient_user, _recipient_user_email_address, wcsg_recipient, or when an agent needs the full WooCommerce Subscriptions switcher/gifting flow.
wcm-data-model-subscriptions-link
WooCommerce Memberships storage and relationship map for membership plan CPTs, user membership CPTs, post statuses, plan/user membership meta keys, rule storage, profile-field storage, order grant meta, and WooCommerce Subscriptions-linked memberships. Use when code reads or writes wc_membership_plan, wc_user_membership, wcm-* statuses, wc_memberships_rules, _subscription_id, _has_installment_plan, _free_trial_end_date, membership plan access meta, user membership dates, product/order grant meta, or when an agent needs exact Memberships CPT/meta names and the Memberships-Subscriptions relation.
wc-variations-pricing-filters
Mutate WooCommerce variation prices via filters — pick the right layer (woocommerce_product_get_price for simple/non-variation products, woocommerce_product_variation_get_price for variations, the woocommerce_variation_prices_* family for the parent min/max aggregation that feeds the catalog "From X to Y" range), and the critical step of filtering woocommerce_get_variation_prices_hash whenever your price logic depends on context outside the default cache key (user role, custom toggle, time of day) — otherwise one user's filtered price gets cached and served to everyone. Use for role-based pricing, time-based discounts, or "filter variation prices but the min/max range doesn't update" debugging. Triggers on woocommerce_product_get_price, woocommerce_product_variation_get_price, woocommerce_variation_prices_*, woocommerce_get_variation_prices_hash, wc_var_prices.
webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
web-artifacts-builder
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.
valyu
Search the live web and 36+ specialised data sources including SEC filings, PubMed, ChEMBL, clinical trials, FRED economic indicators, and patent databases. Use when current, authoritative, or paywalled data is required.
ui-ux-pro-max
AI-powered design intelligence with 67 UI styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 15+ tech stacks. Generates complete design systems for any product type with industry-specific reasoning rules.
theme-factory
Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.
slack-gif-creator
Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."
skill-creator
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
shannon
Autonomous AI security pen testing. Executes real exploits against web applications to find SQL injection, XSS, SSRF, authentication flaws, and IDOR vulnerabilities. Reports only confirmed, reproducible findings — no false positives.
remotion
Create programmatic videos using React and Remotion. Translate natural language descriptions into working Remotion components for product demos, release announcements, explainer videos, and animated content.