GMC CSV Feed for WooCommerce

macdonjo3

Elite Member
Jr. VIP
Joined
Nov 8, 2009
Messages
8,887
Reaction score
9,192
WooCommerce sells their version for $79: https://woocommerce.com/products/google-product-feed/

But this plugin will do it for free.

Save as wp-content/plugins/gmc-csv-feed/gmc-csv-feed.php and activate. your feed will be at /wp-json/gmc/v1/csv

Code:
<?php
/**
 * Plugin Name: GMC CSV Feed for WooCommerce
 * Description: Public CSV feed for Google Merchant Center at /wp-json/gmc/v1/csv
 * Version: 1.0.0
 * Author: You
 */

// Ensure WooCommerce
add_action('plugins_loaded', function () {
    if (!class_exists('WooCommerce')) return;
    add_action('rest_api_init', function () {
        register_rest_route('gmc/v1', '/csv', [
            'methods'  => 'GET',
            'callback' => 'gmc_csv_feed_output',
            'permission_callback' => '__return_true', // public
        ]);
    });
});

function gmc_csv_feed_output(\WP_REST_Request $req) {
    // Headers
    header('Content-Type: text/csv; charset=UTF-8');
    header('Cache-Control: max-age=600, public');

    $out = fopen('php://output', 'w');

    $currency = get_woocommerce_currency();

    // Google Merchant minimal headers
    $headers = [
        'id',
        'title',
        'description',
        'link',
        'image_link',
        'availability',
        'price',
        'brand',
        'condition',
        // add more fields if you want: google_product_category, gtin, mpn, sale_price, etc.
    ];
    fputcsv($out, $headers);

    // Query all published products including variations
    $paged   = 1;
    $perPage = 200;

    while (true) {
        $args = [
            'status'  => 'publish',
            'limit'   => $perPage,
            'page'    => $paged,
            'return'  => 'objects',
            'type'    => ['simple', 'variable'],
        ];
        $products = wc_get_products($args);
        if (empty($products)) break;

        foreach ($products as $product) {
            if ($product->is_type('variable')) {
                foreach ($product->get_children() as $child_id) {
                    $variation = wc_get_product($child_id);
                    if ($variation && $variation->get_status() === 'publish') {
                        gmc_put_product_row($out, $variation, $currency, $product);
                    }
                }
            } else {
                gmc_put_product_row($out, $product, $currency, null);
            }
        }

        $paged++;
    }

    fclose($out);
    exit;
}

function gmc_put_product_row($out, WC_Product $p, string $currency, ?WC_Product $parent) {
    // ID prefers SKU
    $id = $p->get_sku();
    if (!$id) $id = (string)$p->get_id();

    // Title
    $title = $p->get_name();

    // Description - plain text
    $desc = $p->get_description();
    if (!$desc) $desc = $p->get_short_description();
    $desc = trim(preg_replace('/\s+/', ' ', wp_strip_all_tags($desc)));

    // Link
    $link = get_permalink($parent ? $parent->get_id() : $p->get_id());
    // Add variation query args so GMC can land on selected options
    if ($p->is_type('variation')) {
        $attrs = $p->get_attributes();
        if (!empty($attrs)) {
            $link = add_query_arg(array_map('wc_clean', $attrs), $link);
        }
    }

    // Image
    $image_id = $p->get_image_id();
    if (!$image_id && $parent) $image_id = $parent->get_image_id();
    $image = $image_id ? wp_get_attachment_url($image_id) : '';

    // Availability
    $availability = $p->is_in_stock() ? 'in stock' : 'out of stock';

    // Price with currency, tax-inclusive retail price preferred
    $price_val = wc_get_price_including_tax($p, ['price' => $p->get_price()]);
    if ($price_val === '') $price_val = 0;
    $price = wc_format_decimal($price_val, wc_get_price_decimals()) . ' ' . $currency;

    // Brand - try common meta or fallback to site name
    $brand = get_post_meta($p->get_id(), 'brand', true);
    if (!$brand) $brand = get_post_meta($p->get_id(), '_brand', true);
    if (!$brand && $parent) $brand = get_post_meta($parent->get_id(), 'brand', true);
    if (!$brand) $brand = get_bloginfo('name');

    // Condition
    $condition = 'new';

    $row = [
        $id,
        gmc_trim_to($title, 150),
        gmc_trim_to($desc, 5000),
        $link,
        $image,
        $availability,
        $price,
        $brand,
        $condition,
    ];

    fputcsv($out, $row);
}

function gmc_trim_to($text, $max) {
    if (mb_strlen($text) <= $max) return $text;
    return rtrim(mb_substr($text, 0, $max - 1)) . '…';
}
 
That’s a solid share. No need to shell out $79 for Woo’s feed plugin when this does the job—drop it in, activate, and boom, clean CSV feed at /wp-json/gmc/v1/csv. Handles variations, prices, images out of the box, and you can tweak in extras like GTIN if needed.
 
Back
Top