v0.35.0
Added
-
Weight and dimensions now emit on
OfferShippingDetailsand on each variant (#614, #615).- Schema.org defines
weight,height,widthanddepthonOfferShippingDetailsas well as onProduct, and Google draws the same distinction:product_*describes the item,shipping_*describes the parcel. Both now carry the same values. - WooCommerce has exactly one set of dimension fields — filed under the product editor's Shipping tab, and consumed by WooCommerce's own shipping methods to compute rates — so that one set legitimately answers both questions. For a single-item order the numbers are identical; any divergence is packaging overhead, the same approximation Google Merchant Center already accepts.
- Each variant now carries dimensions too. A consumer reading a single
hasVariantentry — the node holding the purchasable offer — previously saw none. Values resolve per variant: its own if set, the parent's otherwise, which is WooCommerce's own getter inheritance rather than logic this plugin wrote. Every variant emits, including inherited values, so a variant read in isolation never looks like it has no shipping data. - Virtual products, whether simple or a variation, continue to emit no shipping/dimension data — the existing
needs_shipping()/has_weight()/has_dimensions()gates are unchanged. Downloadable is a separate WooCommerce checkbox and does not suppress either field: a physical product sold with a bundled download still ships and still carries its dimensions.
- Schema.org defines
-
The six recommended product attributes are now created automatically (#623).
- A fresh WooCommerce store ships with no product attributes at all, so merchants build them ad hoc, name them freely, and type values freely. The plugin now creates
pa_gender,pa_age_group,pa_color,pa_size,pa_materialandpa_patternif they are missing. Seeding is attempted on activation, on every plugin upgrade, and whenever a merchant toggles the syndication enabled setting (the same internal flag that triggers a rewrite-rule flush) — not just on a version change. This is a no-op almost every time, since each attribute is skipped once it exists; the one visible case is a merchant who deliberately deleted one of these attributes seeing it recreated on the next such trigger. Use thewc_ai_storefront_seed_attributesfilter (below) to opt out first if that's not wanted. - Gender and Age group carry Google's complete accepted values (
male/female/unisex, andnewborn/infant/toddler/kids/adult), because Google defines those lists exhaustively. Merchants should not need to add to them. - Color, Size, Material and Pattern carry a small starting set that merchants extend. Google treats these as free text and asks that submitted values match the merchant's own product page, so a canonical list would be wrong. Size deliberately uses abbreviations (e.g.
S,M,L— the full set isXSthrough3XLplusOne size) per Google's consistency guidance, rather than theSmall/Medium/Largeform WooCommerce's own sample data creates. - An attribute that already exists is left completely alone, terms included, decided per attribute rather than all-or-nothing. Existing terms may be variation axes, so adding to or renaming them would break variations and orphan product data.
- New
wc_ai_storefront_seed_attributesfilter returnsfalseto skip seeding entirely.
- A fresh WooCommerce store ships with no product attributes at all, so merchants build them ad hoc, name them freely, and type values freely. The plugin now creates
-
Gender and Age group now emit as a typed
Product.audienceblock (#618).- Google requires
genderandage_groupon all Apparel & Accessories products, and reads them from a typedPeopleAudienceblock, not fromadditionalProperty. A merchant's Gender attribute previously reached the markup only as a genericadditionalPropertyentry — published, but invisible to Google for that purpose. For an apparel product that means disapproval rather than a thinner listing. - Sourced from the
pa_gender/pa_age_groupattributes the plugin now seeds (#623), with a baregender/age_groupcustom attribute accepted as a compatibility fallback. When both forms are present with different values,pa_genderwins over a baregender(same for age group) — order-independent, via apriorityon each map entry. The losing attribute is never discarded; it still emits as its ownadditionalPropertyentry. - Gender is not validated. Any non-empty value emits as
suggestedGender— a value matchingmale/female/unisexis normalised to lowercase, anything else (even a value Google itself rejects, like "Womens") emits trimmed and verbatim.schema:suggestedGenderis Text-ranged, so this is valid markup either way; Google's Merchant Center and Search Console are the intended place to flag a bad value to the merchant, not silent validation here. - Age group IS mapped, and can't follow gender's rule.
suggestedAgeis aQuantitativeValueneedingminValue/maxValue/unitCode, so an unrecognised bucket (e.g. "Grown-up") has no honest numbers to emit and falls back toadditionalPropertyinstead. This is a data-model constraint, not an inconsistency with gender's pass-through behaviour. - Recognised buckets:
newborn(0–3MON),infant(3–12MON),toddler(1–5ANN),kids(5–13ANN),adult(13+ANN, no upper bound — matching Google's own worked example). - Variable products advertise
suggestedGender/suggestedAgeinvariesBywhen that's the varying axis, and each variant carries its own resolvedaudience, inherited per-field from the parent when the variation doesn't define its own.
- Google requires
Fixed
-
Only the dimension axes WooCommerce actually holds are emitted.
has_dimensions()in WooCommerce core is true when any one of length, width or height is set. The emitter published all three off that single gate, and WooCommerce stores an unset axis as an empty string — which casts to0. A merchant who recorded only a height was therefore publishingdepth: 0andwidth: 0: fabricated measurements, presented as real ones.- Each axis is now emitted independently, gated on its own value. A
0the merchant actually typed still emits — the check is for an empty value, not a falsy one, so a deliberate (if unphysical) zero is preserved rather than silently dropped. - Present since dimensions were first emitted, and made more convincing by the numeric cast below:
"depth": ""reads as obviously absent,"depth": 0reads as a measurement. Caught in review before it reached three placements instead of one.
-
Product dimension values now emit as JSON numbers instead of quoted strings (#613).
depth,width, andheightpublished theirQuantitativeValue.valueas a string literal ("value": "10") whileweighton the same product published a number ("value": 1.5). Two adjacent properties in one block disagreed on type.QuantitativeValue.valueis Number-ranged. The markup was not invalid, since the property also acceptsText, but a consumer that doesn't coerce read a string where every sibling gave a number.- The weight cast was added as audit bug #4 for exactly this reason — WooCommerce persists both weight and dimensions as free-form decimal strings (
.5,10).get_dimensions( false )returns those props untouched, and the fix was never applied to the dimension branch. All three now cast through(float). - No change to which products emit dimensions, to unit codes, or to any other field.
Changed
-
Dev tooling: bump
squizlabs/php_codesnifferto 3.13.6 for CVE-2026-67434 (#611). An OS-command-injection advisory (high severity, affecting<3.13.6and>=4.0.0,<4.0.2) was published 2026-08-05 and failed thecomposer auditCI gate on every open PR. The package isrequire-devonly and is not shipped in the plugin zip, so no released version exposed merchants to it; the risk was confined to running lint locally or in CI. -
Dev tooling: clear ten npm advisories from the dev dependency tree (#616). Transitive
devDependenciesonly — nothing in the tree reaches the built bundle. Six alerts remain and are tracked in #596:extract-ziphas no upstream fix at all, andadm-zip,@opentelemetry/coreandwebpack-dev-servereach need a@wordpress/scriptssemver-major that would be a larger change than the risk warrants. All are dev-scope, and the CI security gate scopes npm auditing to production dependencies.
Tests
JsonLdTest.php— newtest_dimension_and_weight_values_encode_as_json_numbersasserts on thewp_json_encodeoutput rather than the array. The pre-existing dimension test usedassertEquals( '10', … ), which passes against both a string and a float under loose comparison and so could not detect this defect in either direction; the quotes are only visible after encoding. Also assertsassertIsFloatper value and that noQuantitativeValueanywhere in the markup encodes its value quoted.