Implementing Product Import from Google Merchant Feed
Google Merchant Feed (GMF) is XML format based on Atom/RSS with g: namespace fields that manufacturers prepare for Google Shopping. For store, import from this format provides access to well-structured data with mandatory id, title, description, price and availability fields.
Feed Structure
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
<channel>
<title>Supplier Product Feed</title>
<item>
<g:id>SKU-12345</g:id>
<g:title>Wireless Headphones Example Pro</g:title>
<g:description>Headphones with noise cancellation, 30 hours battery</g:description>
<g:price>4990 RUB</g:price>
<g:sale_price>3990 RUB</g:sale_price>
<g:availability>in stock</g:availability>
<g:brand>Example</g:brand>
<g:gtin>0012345678901</g:gtin>
<g:color>Black</g:color>
</item>
</channel>
</rss>
Parser with Namespace Support
Key feature — all product fields in g: namespace. SimpleXML requires explicit namespace handling:
class GoogleMerchantFeedParser
{
private const G_NS = 'http://base.google.com/ns/1.0';
public function parse(string $filePath): iterable
{
$reader = new \XMLReader();
$reader->open($filePath);
while ($reader->read()) {
if ($reader->nodeType === \XMLReader::ELEMENT && $reader->name === 'item') {
$node = new \SimpleXMLElement($reader->readOuterXml());
$g = $node->children(self::G_NS);
yield $this->parseItem($g);
}
}
$reader->close();
}
private function parseItem(\SimpleXMLElement $g): array
{
[$price, $currency] = $this->parsePrice((string) $g->price);
return [
'sku' => (string) $g->id,
'name' => (string) $g->title,
'description' => (string) $g->description,
'price' => $price,
'currency' => $currency,
'availability'=> $this->parseAvailability((string) $g->availability),
'brand' => (string) $g->brand,
'gtin' => (string) $g->gtin,
'color' => (string) $g->color,
'images' => array_filter([(string) $g->image_link]),
];
}
private function parsePrice(string $raw): array
{
// "4990 RUB" → [4990.0, 'RUB']
if (preg_match('/^([\d.,]+)\s+([A-Z]{3})$/', trim($raw), $m)) {
return [(float) str_replace(',', '.', $m[1]), $m[2]];
}
return [(float) $raw, 'RUB'];
}
private function parseAvailability(string $raw): string
{
return match (strtolower(trim($raw))) {
'in stock' => 'in_stock',
'out of stock' => 'out_of_stock',
'preorder' => 'preorder',
default => 'unknown',
};
}
}
GTIN-based Deduplication
GTIN (EAN-13, UPC, ISBN) — global identifier for exact product matching:
$product = Product::where('gtin', $offer['gtin'])
->orWhere('sku', $offer['sku'])
->first();
Priority: GTIN > SKU > MPN.
Compressed Feeds (.gz)
Google recommends compressing large feeds. Auto-detect:
private function openFeed(string $url): string
{
if (str_ends_with(parse_url($url, PHP_URL_PATH), '.gz')) {
$gz = gzopen($url, 'rb');
$fp = fopen($tmpFile, 'wb');
while (!gzeof($gz)) fwrite($fp, gzread($gz, 8192));
gzclose($gz);
fclose($fp);
} else {
copy($url, $tmpFile);
}
return $tmpFile;
}
Implementation Timeline
- Parser with namespace, basic fields, availability, price with currency — 1–2 days
- Variable products via item_group_id, Google category mapping — +1–2 days
- GTIN-deduplication, compressed feeds, image download — +1 day







