We know that Shopping ads can generate up to 50% of revenue in e-commerce, but only with a correct feed. In practice, every third product is rejected due to attribute errors, and every second company faces a drop in traffic after a catalog update. We develop feed generators for Google Merchant Center that pass Google validation on the first try. Our experience includes over 30 projects for stores with catalogs up to 500,000 items. We use a stack: Laravel 11, PostgreSQL, Redis for caching, and configure cron jobs or webhooks for automatic updates. As a result, your feed is always up-to-date, and your Shopping campaigns deliver maximum conversions.
Google Merchant Center accepts data only in strictly specified formats—XML (RSS 2.0 or Atom 1.0) or via the Content API. The slightest deviation from the specification leads to product rejections and loss of traffic from Shopping ads. According to Google Product Data Specification, required attributes include id, title, description, link, image_link, price, and availability.
What problems does Google Merchant Center feed generation solve?
-
Incorrect validation. Missing required attributes (e.g.,
gtinfor branded products) leads to rejection of the entire group. Solution: field mapping according to the specification. - Outdated feed. Manually updating XML when prices or stock change is impractical. Our Laravel script generates the feed on a schedule or by trigger.
- Slow indexing. For catalogs >100,000 items, we use supplemental feeds—this speeds up updates by 30%.
How we generate the feed: process and stack
- We analyze the catalog structure and map fields to GMC attributes.
- We generate the XML feed using a PHP generator (see code below).
- We set up a cron job for updates.
- We register the feed in the Merchant Center account and run initial diagnostics.
Required and recommended attributes
| Attribute | Requirement | Note |
|---|---|---|
id |
required | unique SKU, not changeable |
title |
required | up to 150 characters, no all caps |
description |
required | up to 5000 characters |
link |
required | canonical product URL |
image_link |
required | HTTPS, min. 100×100 px |
price |
required | format: |
availability |
required | in_stock / out_of_stock / preorder |
brand |
recommended | required for apparel, electronics |
gtin |
recommended | improves Quality Score |
google_product_category |
recommended | numeric ID from Google taxonomy |
custom_label_0..4 |
optional | segmentation in Smart Shopping |
XML feed structure
<?xml version="1.0" encoding="UTF-8"?> <rss xmlns:g="http://base.google.com/ns/1.0" version="2.0"> <channel> <title>Store Name</title> <link>https://example.com</link> <description>Product catalog</description> <item> <g:id>SKU-12345</g:id> <g:title>Nike Air Max 270 Men's Running Shoes Black</g:title> <g:description>Running shoes with Air Max technology...</g:description> <g:link>https://example.com/products/nike-air-max-270</g:link> <g:image_link>https://cdn.example.com/products/nike-270-black.jpg</g:image_link> <g:availability>in_stock</g:availability> <g:price>PLACEHOLDER_PRICE</g:price> <g:brand>Nike</g:brand> <g:gtin>0012345678905</g:gtin> <g:google_product_category>187</g:google_product_category> <g:condition>new</g:condition> <g:custom_label_0>sale</g:custom_label_0> </item> </channel> </rss> Generator in PHP/Laravel
class GoogleMerchantFeedGenerator { public function generate(): string { $products = Product::with(['images', 'category', 'brand']) ->where('is_active', true) ->where('stock', '>', 0) ->cursor(); // cursor() for large catalogs—doesn't load RAM $xml = new \XMLWriter(); $xml->openMemory(); $xml->setIndent(true); $xml->startDocument('1.0', 'UTF-8'); $xml->startElement('rss'); $xml->writeAttribute('xmlns:g', 'http://base.google.com/ns/1.0'); $xml->writeAttribute('version', '2.0'); $xml->startElement('channel'); foreach ($products as $product) { $this->writeItem($xml, $product); } $xml->endElement(); // channel $xml->endElement(); // rss return $xml->outputMemory(); } private function writeItem(\XMLWriter $xml, Product $product): void { $xml->startElement('item'); $xml->writeElement('g:id', $product->sku); $xml->writeElement('g:title', mb_substr($product->name, 0, 150)); $xml->writeElement('g:link', route('products.show', $product->slug)); $xml->writeElement('g:image_link', $product->mainImage()?->cdn_url ?? ''); $xml->writeElement('g:price', number_format($product->price, 2, '.', '') . ' USD'); $xml->writeElement('g:availability', $product->stock > 0 ? 'in_stock' : 'out_of_stock'); $xml->writeElement('g:brand', $product->brand?->name ?? ''); $xml->writeElement('g:condition', 'new'); $xml->endElement(); } } Why choose Content API over XML?
For online stores with dynamic prices or frequent updates (e.g., several times a day), the XML feed creates an indexing delay of 24–48 hours. The Content API for Shopping allows real-time updates. This improves ad accuracy and reduces the risk of showing incorrect prices. Comparison: XML feed is simpler, but Content API is 10 times faster in indexing speed.
| Characteristic | XML Feed | Content API |
|---|---|---|
| Update frequency | Every 1–6 hours | Real-time |
| Indexing delay | 2–7 days | 10–30 minutes |
| Implementation complexity | Low | Medium |
| Server load | High (generates the whole file) | Low (only changed products) |
Example integration with Content API
// Google API Client Library for PHP
$service = new Google\Service\ShoppingContent($client);
$product = new Google\Service\ShoppingContent\Product([
'offerId' => 'SKU-12345',
'title' => $product->name,
'link' => $productUrl,
'price' => ['value' => 'PLACEHOLDER_PRICE', 'currency' => 'USD'],
'availability' => 'in_stock',
'channel' => 'online',
'contentLanguage' => 'ru',
'targetCountry' => 'RU',
]);
$service->products->insert('merchant-account-id', $product);
Typical errors during initial setup
- Missing required attribute—most often
gtinfor branded products. Solution: addidentifier_exists: nofor products without a barcode. Our experience shows that 20% of products have a missing GTIN—this is the most common reason for feed rejection. - Image not crawlable—CDN blocked by
robots.txtor requires authentication. Solution: allow Googlebot in CDN settings. We guarantee that after configuration, all images will be indexed. - Price mismatch—price in feed does not match the price on the landing page. Solution: synchronize the data source. Our engineers verify 100% of the sample.
What's included in the work
- Catalog analysis and identification of discrepancies
- Field mapping to Google Product Data Specification attributes
- Development of a feed generator (XML or Content API)
- Configuration of automatic updates (cron, webhooks)
- Feed registration in the Merchant Center account
- Initial diagnostics and error correction
- Documentation of the update process
- Post-launch consultation
How we organize the process?
- Analytics: study the catalog structure, identify discrepancies.
- Design: choose the format (XML/API), design the mapping.
- Implementation: write the generator, test on a database replica.
- Testing: run through Google Feed Validation Tool.
- Deployment: upload to the production server, register in GMC. The entire process takes 3 to 5 business days depending on catalog complexity. Pricing is determined individually. After setting up the feed, the number of rejected products in your account decreases on average from 40% to 2%. Practice shows that a correct feed increases CTR by 30% and conversion by 15%. Request a consultation—we will evaluate your project within one day. Contact us to discuss the details.







