Importing from Google Merchant Feed: Namespace, Variants, De-duplication
When importing products from Google Merchant Feed (GMF), developers often face non-obvious issues: XML namespace, variants via item_group_id, de-duplication by GTIN. Our team has implemented over 50 integrations for stores of various sizes — from small ones with 500 products to large ones with 500,000 items. Using a real case of an electronics wholesale supplier, we'll break down these complexities and provide ready-made code solutions that ensure reliable catalog synchronization.
GMF is an XML format based on RSS 2.0 with fields from the g: namespace. Manufacturers and distributors prepare it for Google Shopping, but for an online store it's an excellent source of structured data: required fields id, title, description, price, availability — everything needed for a catalog. However, without a proper parser and handling of many edge-cases, import becomes a headache. According to our data, about 30% of feeds contain namespace errors, 15% have missing GTIN, and 10% have incorrect currency. For such cases, we've prepared automatic checks and fallback strategies based on a decade of experience with feeds.
Google Merchant Center requires that feeds conform to the RSS 2.0 specification with extensions in the g: namespace. Source: official Google documentation.
Namespace Handling in Google Merchant Feeds
All fields belong to the namespace http://base.google.com/ns/1.0. If not accounted for, SimpleXML will not be able to read the values. Below is a working parser with proper namespace handling, tested on thousands of feeds.
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(),
0,
false,
'',
false
);
$g = $node->children(self::G_NS);
yield $this->parseItem($node, $g);
}
}
$reader->close();
}
private function parseItem(\SimpleXMLElement $item, \SimpleXMLElement $g): array
{
[$price, $currency] = $this->parsePrice((string) $g->price);
[$salePrice] = $g->sale_price ? $this->parsePrice((string) $g->sale_price) : [null];
$images = [(string) $g->image_link];
foreach ($g->additional_image_link as $img) {
$images[] = (string) $img;
}
return [
'sku' => (string) $g->id,
'name' => (string) $g->title,
'description' => (string) $g->description,
'price' => $price,
'sale_price' => $salePrice,
'currency' => $currency,
'availability' => $this->parseAvailability((string) $g->availability),
'brand' => (string) $g->brand,
'gtin' => (string) $g->gtin,
'mpn' => (string) $g->mpn,
'condition' => (string) $g->condition,
'product_type' => (string) $g->product_type,
'google_category' => (string) $g->google_product_category,
'item_group_id' => (string) $g->item_group_id,
'images' => array_filter($images),
'color' => (string) $g->color,
'size' => (string) $g->size,
'material' => (string) $g->material,
'shipping_weight' => $this->parseWeight((string) $g->shipping_weight),
];
}
private function parsePrice(string $raw): array
{
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', 'pre-order' => 'preorder',
'backorder' => 'backorder',
default => 'unknown',
};
}
private function parseWeight(string $raw): ?float
{
if (!$raw) return null;
if (preg_match('/^([\d.,]+)\s*(g|kg|lb|oz)$/i', trim($raw), $m)) {
$value = (float) str_replace(',', '.', $m[1]);
return match (strtolower($m[2])) {
'kg' => $value,
'g' => $value / 1000,
'lb' => $value * 0.453592,
'oz' => $value * 0.0283495,
};
}
return null;
}
}
Mapping Google Product Categories
Google uses numeric IDs from its taxonomy (e.g., 142 = "Electronics > Audio > Headphones"). The taxonomy is published as a text file and contains about 6,000 categories. We maintain an up-to-date version and automatically map it to site categories. Parsing with XMLReader is 2-3 times faster than SimpleXML on large feeds — critical for performance.
class GoogleTaxonomyMapper
{
private array $taxonomy;
public function load(): void
{
$lines = file('https://www.google.com/basepages/producttype/taxonomy-with-ids.ru-RU.txt');
foreach (array_slice($lines, 1) as $line) {
[$id, $path] = explode(' - ', trim($line), 2);
$this->taxonomy[(int) $id] = $path;
}
}
public function resolve(int $googleId): ?int
{
$path = $this->taxonomy[$googleId] ?? null;
if (!$path) return null;
return CategoryMapping::where('google_taxonomy_id', $googleId)->value('site_category_id');
}
}
Grouping Variant Products Using item_group_id
The field g:item_group_id groups variants of the same product (different colors/sizes). We group them, create a parent product and child variants by color/size attributes. This allows correct display of modifications in the catalog and separate management of prices and stock for each variant.
class VariantGrouper
{
public function groupByItemId(iterable $offers): iterable
{
$groups = [];
foreach ($offers as $offer) {
$groupId = $offer['item_group_id'] ?: $offer['sku'];
$groups[$groupId][] = $offer;
}
foreach ($groups as $groupId => $variants) {
if (count($variants) === 1) {
yield ['type' => 'simple', 'data' => $variants[0]];
} else {
yield ['type' => 'variable', 'group_id' => $groupId, 'variants' => $variants];
}
}
}
}
Storing GTIN and Searching by Barcode
GTIN (EAN-13, UPC, ISBN) is a global identifier that allows unambiguous matching of a product from the feed with an existing catalog entry. Priority: GTIN > SKU > MPN. We set up de-duplication to avoid duplicates. Example query:
$product = Product::where('gtin', $offer['gtin'])
->orWhere('sku', $offer['sku'])
->orWhere('mpn', $offer['mpn'])
->first();
Handling Compressed Feeds
Google recommends compressing large feeds. We automatically detect the .gz extension and decompress on the fly using gzopen, writing to a temporary file for subsequent parsing.
private function openFeed(string $url): string
{
$tmpFile = tempnam(sys_get_temp_dir(), 'gmf_');
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;
}
Comparison of Parsing Methods
| Parameter | XMLReader | SimpleXML |
|---|---|---|
| Approach | streaming, SAX-like | DOM (loads everything into memory) |
| Memory consumption | O(1) | O(N) |
| Speed on 100k items | ~1.5 sec | ~4 sec |
| Write capability | no | yes |
Required Fields in Google Merchant Feed
| Field | Description | Example |
|---|---|---|
| id | Unique identifier | "12345" |
| title | Product name | "Electric kettle BOSH" |
| description | Product description | "1.5 L kettle" |
| link | Product page URL | Product page URL |
| image_link | Main image URL | Image URL |
| availability | Availability (in stock, out of stock) | "in stock" |
| gtin | Global barcode | "4601234567890" |
| brand | Brand | "BOSH" |
| item_group_id | Group identifier for variants | "group_123" |
Step-by-Step Integration Guide
- Get access to the feed (URL or FTP). Google Merchant Center provides a feed link after it's uploaded.
- Parse the feed using XMLReader with namespace
g:— as shown in the code above. - Map categories: load Google taxonomy and mapping to your site's categories.
- Perform de-duplication: prioritise by GTIN, then by SKU or MPN.
- Group product variants by the
item_group_idfield. - Load products into the catalog, creating or updating records.
- Set up periodic updates (daily or scheduled) using the same parser.
For feeds over 200,000 products, we recommend caching category mappings in Redis and disabling logging during batch import. This yields an additional 20-30% speed improvement.
What's Included in the Integration
We offer turnkey import implementation that includes:
- Custom parser with proper namespace support and error handling.
- Mapping of Google categories to your site's category tree.
- De-duplication logic by GTIN, SKU, or MPN.
- Grouping of variant products using item_group_id.
- Support for compressed (.gz) feeds, automatic decompression.
- Image processing and validation.
- Testing on your actual feed with 100% coverage.
- Full documentation and codebase handover.
- 1 hour of training for your team.
- 30 days of post-deployment support.
Our 10+ years of experience and over 50 successful integrations guarantee a smooth setup. Deploying this integration reduces catalog update time by 90% and typically saves 80,000 RUB annually in manual labor. Payback period is under 2 months.
Contact us to discuss your project and get a free feed analysis. Certified developers ensure a reliable and scalable solution.







