Integration of 1C-Bitrix with Google Shopping Ads

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

1C-Bitrix Integration with Google Shopping Ads

Google Shopping displays product ads with an image, price, and title directly in the search results. The required chain is: Bitrix → Google Merchant Center → Google Ads. The weak link is the feed: if prices in the feed differ from prices on the site, Google rejects products and ads stop showing. Integration is not a one-time export — it is an automated feed update with monitoring of product statuses in Merchant Center.

Integration Architecture

[Bitrix catalog]
      ↓
[Feed generator: PHP script or module]
      ↓
[XML feed on hosting / S3]
      ↓
[Google Merchant Center: scheduled import]
      ↓
[Google Ads: Shopping campaigns]

The feed is updated on the Bitrix side; Google Merchant Center fetches it from a URL at a defined frequency (every 24–72 hours or forcefully via the Content API).

Google Shopping Feed Structure (Merchant Center)

Google requires the Google Shopping XML format (or TSV). Required fields:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
  <channel>
    <title>My Store</title>
    <link>https://your-site.com</link>
    <item>
      <g:id>PRODUCT-SKU-123</g:id>
      <g:title>Product Title (150 chars max)</g:title>
      <g:description>Detailed description for the ad</g:description>
      <g:link>https://your-site.com/catalog/product-123/</g:link>
      <g:image_link>https://your-site.com/upload/img/product-123.jpg</g:image_link>
      <g:price>1990 USD</g:price>
      <g:sale_price>1490 USD</g:sale_price>  <!-- if on sale -->
      <g:availability>in stock</g:availability>
      <g:condition>new</g:condition>
      <g:brand>BrandName</g:brand>
      <g:gtin>4607086560001</g:gtin>  <!-- barcode, EAN -->
      <g:mpn>ART-0001</g:mpn>         <!-- article number, if no GTIN -->
      <g:product_type>Electronics &gt; Smartphones</g:product_type>
      <g:google_product_category>5032</g:google_product_category>
      <g:shipping>
        <g:country>US</g:country>
        <g:service>Courier</g:service>
        <g:price>5 USD</g:price>
      </g:shipping>
    </item>
  </channel>
</rss>

g:google_product_category — a numeric ID from the Google taxonomy (downloadable from support.google.com). An incorrect category reduces display relevance.

Feed Generator from Bitrix Catalog

// /local/php_interface/cron/google_shopping_feed.php
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');

\Bitrix\Main\Loader::includeModule('iblock');
\Bitrix\Main\Loader::includeModule('catalog');

$IBLOCK_ID = 10;  // Catalog ID
$outputFile = $_SERVER['DOCUMENT_ROOT'] . '/feeds/google_shopping.xml';

$xml = new XMLWriter();
$xml->openUri($outputFile);
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('rss');
$xml->writeAttribute('version', '2.0');
$xml->writeAttribute('xmlns:g', 'http://base.google.com/ns/1.0');
$xml->startElement('channel');
$xml->writeElement('title', 'My Store');
$xml->writeElement('link', 'https://your-site.com');

// Query catalog elements
$elements = \CIBlockElement::GetList(
    ['SORT' => 'ASC'],
    [
        'IBLOCK_ID' => $IBLOCK_ID,
        'ACTIVE'    => 'Y',
        '!CATALOG_QUANTITY' => 0,  // in stock only
    ],
    false,
    false,
    ['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_TEXT', 'DETAIL_TEXT']
);

while ($el = $elements->GetNextElement()) {
    $fields = $el->GetFields();
    $props  = $el->GetProperties();

    // Get price
    $price = \CPrice::GetBasePrice($fields['ID']);

    // Get image
    $image = \CFile::GetPath($fields['PREVIEW_PICTURE'] ?: $fields['DETAIL_PICTURE']);

    // Availability
    $productData = \CCatalogProduct::GetByIDEx($fields['ID']);
    $availability = ($productData['QUANTITY'] > 0) ? 'in stock' : 'out of stock';

    $xml->startElement('item');
    $xml->writeElement('g:id', $fields['ID']);
    $xml->writeElement('g:title', substr(strip_tags($fields['NAME']), 0, 150));
    $xml->writeElement('g:description', substr(strip_tags($fields['PREVIEW_TEXT'] ?: $fields['DETAIL_TEXT']), 0, 5000));
    $xml->writeElement('g:link', 'https://your-site.com' . $fields['DETAIL_PAGE_URL']);

    if ($image) {
        $xml->writeElement('g:image_link', 'https://your-site.com' . $image);
    }

    if ($price) {
        $xml->writeElement('g:price', number_format($price['PRICE'], 2, '.', '') . ' ' . $price['CURRENCY']);
    }

    $xml->writeElement('g:availability', $availability);
    $xml->writeElement('g:condition', 'new');

    if (!empty($props['BRAND']['VALUE'])) {
        $xml->writeElement('g:brand', $props['BRAND']['VALUE']);
    }

    if (!empty($props['BARCODE']['VALUE'])) {
        $xml->writeElement('g:gtin', $props['BARCODE']['VALUE']);
    } elseif (!empty($props['ARTICLE']['VALUE'])) {
        $xml->writeElement('g:mpn', $props['ARTICLE']['VALUE']);
        $xml->writeElement('g:identifier_exists', 'no');
    }

    $xml->endElement(); // item
}

$xml->endElement(); // channel
$xml->endElement(); // rss
$xml->endDocument();
$xml->flush();

The script runs via cron every 2–4 hours. The feed file is accessible at the URL https://your-site.com/feeds/google_shopping.xml.

Content API: Forcing Updates for Individual Products

When a price or stock level changes, waiting for the next feed scan is impractical — Google may show an outdated price for a full day. The Content API allows updating a specific product instantly:

// composer require google/apiclient
$client = new \Google\Client();
$client->setAuthConfig('/path/to/service-account.json');
$client->addScope(\Google\Service\ShoppingContent::CONTENT);

$service = new \Google\Service\ShoppingContent($client);

$product = new \Google\Service\ShoppingContent\Product([
    'offerId'      => 'PRODUCT-SKU-123',
    'price'        => new \Google\Service\ShoppingContent\Price([
        'value'    => '1490.00',
        'currency' => 'USD',
    ]),
    'availability' => 'in stock',
]);

$service->products->update($merchantId, 'online:en:US:PRODUCT-SKU-123', $product);

On the OnAfterCatalogProductUpdate event in Bitrix — call the Content API update for the specific product. This adds ~100–200 ms to the save operation, but the price in Google updates within a few minutes.

Common Reasons for Product Rejection in Merchant Center

  • price mismatch — the price in the feed does not match the price on the product page. Google crawls the site separately and compares. Most commonly caused by discounts not reflected in the feed, or a cached page with an outdated price
  • missing required attribute [gtin] — no barcode. For many categories GTIN is mandatory. If it is absent — add identifier_exists: no
  • image too small — image is smaller than 100×100 px (250×250 for clothing). Bitrix stores images in upload/, original dimensions must be verified
  • landing page not crawlable — the product page is inaccessible to the Google bot. Check robots.txt and noindex

Development Timeline

Stage Contents Duration
Catalog field mapping Mapping Bitrix fields/properties → feed fields 1 day
Feed generator PHP script + cron 2–3 days
Merchant Center setup Feed upload, site verification, taxes/shipping 1 day
Content API integration Update on price/stock change 2–3 days
Diagnostics and error fixing Resolving rejection causes 1–2 days