Created
October 6, 2025 09:40
-
-
Save Auke1810/34851fdd8740482d2b3d04a68d5bef47 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// OPTIONAL: disable WooCommerce's built-in product schema to avoid duplicates | |
// add_filter('woocommerce_structured_data_product', '__return_false', 9999); | |
add_action('wp_head', 'my_add_product_structured_data', 99); | |
function my_add_product_structured_data() { | |
if ( ! is_product() ) { | |
return; | |
} | |
// Always resolve the product explicitly (wp_head runs early) | |
$product_id = get_queried_object_id(); | |
if ( ! $product_id ) { | |
return; | |
} | |
$product = wc_get_product( $product_id ); | |
if ( ! $product ) { | |
return; | |
} | |
// Basic fields | |
$name = $product->get_name(); | |
$desc = wp_strip_all_tags( $product->get_description() ?: $product->get_short_description() ); | |
$permalink = get_permalink( $product->get_id() ); | |
$currency = get_woocommerce_currency(); | |
$in_stock = $product->is_in_stock(); | |
$sku = $product->get_sku(); | |
// Images: primary + a few gallery images | |
$images = []; | |
$main_img = wp_get_attachment_image_url( $product->get_image_id(), 'full' ); | |
if ( $main_img ) { | |
$images[] = $main_img; | |
} | |
foreach ( array_slice( (array) $product->get_gallery_image_ids(), 0, 4 ) as $att_id ) { | |
$url = wp_get_attachment_image_url( $att_id, 'full' ); | |
if ( $url ) { | |
$images[] = $url; | |
} | |
} | |
// Fallback placeholder if truly no images | |
if ( empty( $images ) ) { | |
$placeholder = wc_placeholder_img_src( 'full' ); | |
if ( $placeholder ) { | |
$images[] = $placeholder; | |
} | |
} | |
// Brand (custom meta) — adjust meta keys to your store | |
$brand = get_post_meta( $product->get_id(), '_product_brand', true ); | |
$brand_schema = $brand ? [ '@type' => 'Brand', 'name' => $brand ] : null; | |
// GTIN (custom meta) — support common keys | |
$gtin = get_post_meta( $product->get_id(), '_product_gtin', true ); | |
if ( ! $gtin ) { | |
$gtin = get_post_meta( $product->get_id(), '_wc_gpf_gtin', true ); // example alt key | |
} | |
// Optional: set a price validity (e.g., 30 days from today) for better completeness | |
$price_valid_until = gmdate( 'Y-m-d', strtotime( '+30 days' ) ); | |
// Build offers | |
$offers = null; | |
if ( $product->is_type( 'variable' ) ) { | |
$children = $product->get_children(); | |
$variations = []; | |
$prices = []; | |
foreach ( $children as $variation_id ) { | |
$v = wc_get_product( $variation_id ); | |
if ( ! $v ) { | |
continue; | |
} | |
$price = $v->get_price(); | |
if ( $price === '' ) { | |
continue; | |
} | |
$prices[] = (float) $price; | |
$variations[] = [ | |
'@type' => 'Offer', | |
'url' => get_permalink( $variation_id ), | |
'sku' => $v->get_sku() ?: null, | |
'priceCurrency' => $currency, | |
'price' => wc_format_decimal( $price, wc_get_price_decimals() ), | |
'availability' => $v->is_in_stock() ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock', | |
'itemCondition' => 'https://schema.org/NewCondition', | |
'priceValidUntil' => $price_valid_until, | |
]; | |
} | |
$prices = array_filter( $prices, 'is_numeric' ); | |
if ( ! empty( $prices ) ) { | |
$offers = [ | |
'@type' => 'AggregateOffer', | |
'priceCurrency' => $currency, | |
'lowPrice' => wc_format_decimal( min( $prices ), wc_get_price_decimals() ), | |
'highPrice' => wc_format_decimal( max( $prices ), wc_get_price_decimals() ), | |
'offerCount' => count( $variations ), | |
'offers' => $variations, | |
]; | |
} | |
} else { | |
// Simple/grouped/external – single offer | |
$price = $product->get_price(); | |
if ( $price !== '' ) { | |
$offers = [ | |
'@type' => 'Offer', | |
'url' => $permalink, | |
'priceCurrency' => $currency, | |
'price' => wc_format_decimal( $price, wc_get_price_decimals() ), | |
'availability' => $in_stock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock', | |
'itemCondition' => 'https://schema.org/NewCondition', | |
'priceValidUntil' => $price_valid_until, | |
]; | |
} | |
} | |
// Aggregate rating | |
$rating_count = (int) $product->get_rating_count(); | |
$aggregate_rating = null; | |
if ( $rating_count > 0 ) { | |
$aggregate_rating = [ | |
'@type' => 'AggregateRating', | |
'ratingValue' => (string) $product->get_average_rating(), | |
'reviewCount' => $rating_count, | |
]; | |
} | |
// Compose schema | |
$schema = [ | |
'@context' => 'https://schema.org', | |
'@type' => 'Product', | |
'@id' => trailingslashit( $permalink ) . '#product', | |
'name' => $name, | |
'description' => $desc, | |
'image' => array_values( array_unique( $images ) ), | |
'sku' => $sku ?: null, | |
'brand' => $brand_schema, | |
'offers' => $offers, | |
'aggregateRating' => $aggregate_rating, | |
'itemCondition' => 'https://schema.org/NewCondition', // also valid at Product level | |
]; | |
// GTIN: choose the most appropriate property (gtin13 here) | |
if ( $gtin ) { | |
$schema['gtin13'] = (string) $gtin; | |
} | |
// Clean nulls | |
$schema = array_filter( $schema, function( $v ) { return ! is_null( $v ); } ); | |
echo '<script type="application/ld+json">' . esc_html( wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) ) . '</script>' . "\n"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment