Skip to content

Instantly share code, notes, and snippets.

@Auke1810
Created October 6, 2025 09:40
Show Gist options
  • Save Auke1810/34851fdd8740482d2b3d04a68d5bef47 to your computer and use it in GitHub Desktop.
Save Auke1810/34851fdd8740482d2b3d04a68d5bef47 to your computer and use it in GitHub Desktop.
<?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