Commit 848aa4af60c for woocommerce
commit 848aa4af60c2e94e32b706bfb9d89e05b672d332
Author: Bero <1199991+bgrgicak@users.noreply.github.com>
Date: Fri Oct 30 08:58:19 2026 +0100
Add product weight and dimensions to structured data (#70360)
* Add product measurements to structured data
* Add changelog entry for product schema measurements
* Preserve store units in product structured data
* Add product measurement structured data tests
diff --git a/plugins/woocommerce/changelog/add-product-schema-measurements b/plugins/woocommerce/changelog/add-product-schema-measurements
new file mode 100644
index 00000000000..f49e65f080f
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-product-schema-measurements
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Include product weight and dimensions in structured data so shopping agents can compare products.
diff --git a/plugins/woocommerce/includes/class-wc-structured-data.php b/plugins/woocommerce/includes/class-wc-structured-data.php
index d9cca8be83d..d22c2130b29 100644
--- a/plugins/woocommerce/includes/class-wc-structured-data.php
+++ b/plugins/woocommerce/includes/class-wc-structured-data.php
@@ -15,6 +15,8 @@
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Enums\ProductStatus;
use Automattic\WooCommerce\Enums\ProductType;
+use Automattic\WooCommerce\Enums\DimensionUnit;
+use Automattic\WooCommerce\Enums\WeightUnit;
use Automattic\WooCommerce\Enums\ProductStockStatus;
use Automattic\WooCommerce\Enums\TaxDisplayMode;
@@ -560,6 +562,41 @@ class WC_Structured_Data {
return;
}
+ $measurement_product = $selected_variation ?? $product;
+ $weight_units = array(
+ WeightUnit::KILOGRAM => 'KGM',
+ WeightUnit::GRAM => 'GRM',
+ WeightUnit::POUND => 'LBR',
+ WeightUnit::OUNCE => 'ONZ',
+ );
+ $dimension_units = array(
+ DimensionUnit::METER => 'MTR',
+ DimensionUnit::CENTIMETER => 'CMT',
+ DimensionUnit::MILLIMETER => 'MMT',
+ DimensionUnit::INCH => 'INH',
+ DimensionUnit::YARD => 'YRD',
+ );
+ $weight_unit = get_option( 'woocommerce_weight_unit' );
+ $dimension_unit = get_option( 'woocommerce_dimension_unit' );
+ $measurements = array(
+ 'weight' => $measurement_product->get_weight(),
+ 'width' => $measurement_product->get_width(),
+ 'height' => $measurement_product->get_height(),
+ 'depth' => $measurement_product->get_length(),
+ );
+ foreach ( $measurements as $property => $value ) {
+ $unit = 'weight' === $property ? $weight_unit : $dimension_unit;
+ $units = 'weight' === $property ? $weight_units : $dimension_units;
+ if ( ! is_numeric( $value ) || ! is_finite( (float) $value ) || (float) $value <= 0 || ! is_string( $unit ) || ! isset( $units[ $unit ] ) || isset( $markup[ $property ] ) ) {
+ continue;
+ }
+ $markup[ $property ] = array(
+ '@type' => 'QuantitativeValue',
+ 'value' => (float) $value,
+ 'unitCode' => $units[ $unit ],
+ );
+ }
+
$this->set_data( apply_filters( 'woocommerce_structured_data_product', $markup, $product ) );
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php b/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php
index a3b4beb9047..5f30e14b41f 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-structured-data-test.php
@@ -412,6 +412,196 @@ class WC_Structured_Data_Test extends \WC_Unit_Test_Case {
}
}
+ /**
+ * @testdox Product measurements use the configured unit codes and map length to depth.
+ * @testWith ["kg", "KGM", "m", "MTR"]
+ * ["g", "GRM", "cm", "CMT"]
+ * ["lbs", "LBR", "mm", "MMT"]
+ * ["oz", "ONZ", "in", "INH"]
+ * ["kg", "KGM", "yd", "YRD"]
+ *
+ * @param string $weight_unit Weight unit.
+ * @param string $weight_code Expected weight code.
+ * @param string $dimension_unit Dimension unit.
+ * @param string $dimension_code Expected dimension code.
+ */
+ public function test_product_measurement_unit_codes( string $weight_unit, string $weight_code, string $dimension_unit, string $dimension_code ): void {
+ update_option( 'woocommerce_weight_unit', $weight_unit );
+ update_option( 'woocommerce_dimension_unit', $dimension_unit );
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_weight( '2.5' );
+ $product->set_length( '3' );
+ $product->set_width( '4' );
+ $product->set_height( '5' );
+
+ $sut = new WC_Structured_Data();
+ $sut->generate_product_data( $product );
+ $data = $sut->get_data()[0];
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 2.5,
+ 'unitCode' => $weight_code,
+ ),
+ $data['weight']
+ );
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 3.0,
+ 'unitCode' => $dimension_code,
+ ),
+ $data['depth']
+ );
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 4.0,
+ 'unitCode' => $dimension_code,
+ ),
+ $data['width']
+ );
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 5.0,
+ 'unitCode' => $dimension_code,
+ ),
+ $data['height']
+ );
+ }
+
+ /**
+ * @testdox Empty, non-positive and invalid measurements are omitted from product schema.
+ * @testWith [""]
+ * ["0"]
+ * ["-1"]
+ * ["invalid"]
+ * ["1e999"]
+ *
+ * @param string $value Measurement returned by a product filter.
+ */
+ public function test_invalid_product_measurements_are_omitted( string $value ): void {
+ $product = WC_Helper_Product::create_simple_product();
+ add_filter(
+ 'woocommerce_product_get_weight',
+ static function () use ( $value ) {
+ return $value;
+ }
+ );
+ add_filter(
+ 'woocommerce_product_get_length',
+ static function () use ( $value ) {
+ return $value;
+ }
+ );
+ add_filter(
+ 'woocommerce_product_get_width',
+ static function () use ( $value ) {
+ return $value;
+ }
+ );
+ add_filter(
+ 'woocommerce_product_get_height',
+ static function () use ( $value ) {
+ return $value;
+ }
+ );
+ $sut = new WC_Structured_Data();
+ $sut->generate_product_data( $product );
+ $data = $sut->get_data()[0];
+ foreach ( array( 'weight', 'depth', 'width', 'height' ) as $property ) {
+ $this->assertArrayNotHasKey( $property, $data );
+ }
+ }
+
+ /**
+ * @testdox Unsupported units omit their measurements without affecting other units.
+ * @testWith ["unknown", "cm", false, true]
+ * ["kg", "unknown", true, false]
+ *
+ * @param string $weight_unit Weight unit.
+ * @param string $dimension_unit Dimension unit.
+ * @param bool $has_weight Whether weight is expected.
+ * @param bool $has_dimensions Whether dimensions are expected.
+ */
+ public function test_unsupported_measurement_units( string $weight_unit, string $dimension_unit, bool $has_weight, bool $has_dimensions ): void {
+ update_option( 'woocommerce_weight_unit', $weight_unit );
+ update_option( 'woocommerce_dimension_unit', $dimension_unit );
+ $product = WC_Helper_Product::create_simple_product();
+ $product->set_weight( '2' );
+ $product->set_length( '3' );
+ $sut = new WC_Structured_Data();
+ $sut->generate_product_data( $product );
+ $data = $sut->get_data()[0];
+ $this->assertSame( $has_weight, isset( $data['weight'] ) );
+ $this->assertSame( $has_dimensions, isset( $data['depth'] ) );
+ }
+
+ /**
+ * @testdox Selected variations use their own measurements and inherit missing values from the parent.
+ */
+ public function test_selected_variation_measurements(): void {
+ update_option( 'woocommerce_weight_unit', 'kg' );
+ update_option( 'woocommerce_dimension_unit', 'cm' );
+ $product = WC_Helper_Product::create_variation_product();
+ $product->set_weight( '2' );
+ $product->set_length( '3' );
+ $product->set_width( '4' );
+ $product->set_height( '5' );
+ $product->save();
+ $variation = wc_get_product( wc_get_product_id_by_sku( 'DUMMY SKU VARIABLE HUGE RED 0' ) );
+ $variation->set_weight( '7' );
+ $variation->set_length( '' );
+ $variation->set_width( '8' );
+ $variation->set_height( '' );
+ $variation->save();
+ WC_Product_Variable::sync( $product->get_id() );
+ $original_get = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Preserve the request state for this test.
+ try {
+ $_GET['attribute_pa_size'] = 'huge';
+ $_GET['attribute_pa_colour'] = 'red';
+ $_GET['attribute_pa_number'] = '0';
+ $sut = new WC_Structured_Data();
+ $sut->generate_product_data( wc_get_product( $product->get_id() ) );
+ $data = $sut->get_data()[0];
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 7.0,
+ 'unitCode' => 'KGM',
+ ),
+ $data['weight']
+ );
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 3.0,
+ 'unitCode' => 'CMT',
+ ),
+ $data['depth']
+ );
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 8.0,
+ 'unitCode' => 'CMT',
+ ),
+ $data['width']
+ );
+ $this->assertSame(
+ array(
+ '@type' => 'QuantitativeValue',
+ 'value' => 5.0,
+ 'unitCode' => 'CMT',
+ ),
+ $data['height']
+ );
+ } finally {
+ $_GET = $original_get;
+ }
+ }
+
/**
* Test simple product offer structured data includes offer-level price currency.
*