Integration of 1C-Bitrix with the Pimcore PIM system

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

Integration of 1C-Bitrix with Pimcore PIM System

Pimcore is an open-source platform combining PIM (product management), DAM (digital assets), and CMS. Unlike Akeneo, Pimcore can be deployed on its own server without cloud subscription, which is important for projects with closed infrastructure. Integration with Bitrix is built through Pimcore REST API or direct database interaction when both systems are on the same network.

Pimcore Data Objects and REST API

In Pimcore, products are stored as Data Objects — structured objects with fields (Field Definitions). For each object class (Product, Variant, Category), Pimcore automatically generates a REST API:

GET  /api/objects?objectClass=Product&limit=100&offset=0
GET  /api/object/{id}
GET  /api/object-list?objectClass=Product&q={"active":true}
GET  /api/asset/{id}             — media files
GET  /api/asset-list?q={"type":"image"}

Authentication is via Basic Auth or API Key in header:

class PimcoreClient
{
    private string $baseUrl;
    private array  $authHeaders;

    public function __construct(string $baseUrl, string $apiKey)
    {
        $this->baseUrl     = rtrim($baseUrl, '/');
        $this->authHeaders = ['X-API-Key' => $apiKey];
    }

    public function getObjects(
        string $class,
        int $offset = 0,
        int $limit = 100,
        ?string $filter = null
    ): array {
        $http = new \Bitrix\Main\Web\HttpClient();
        foreach ($this->authHeaders as $k => $v) {
            $http->setHeader($k, $v);
        }

        $url = $this->baseUrl . '/api/objects'
             . '?objectClass=' . urlencode($class)
             . '&offset=' . $offset
             . '&limit=' . $limit;

        if ($filter) {
            $url .= '&q=' . urlencode($filter);
        }

        $response = $http->get($url);
        $data     = json_decode($response, true);

        return $data['data'] ?? [];
    }

    public function getAsset(int $assetId): ?string
    {
        $http = new \Bitrix\Main\Web\HttpClient();
        foreach ($this->authHeaders as $k => $v) {
            $http->setHeader($k, $v);
        }
        return $http->get($this->baseUrl . '/api/asset/' . $assetId . '/download') ?: null;
    }
}

Data Structure in Pimcore

A Data Object class Product contains fields defined in the Pimcore administrative interface. Typical e-commerce structure:

{
  "id": 1234,
  "className": "Product",
  "elements": [
    {"name": "sku",          "type": "input",    "value": "PROD-001"},
    {"name": "name",         "type": "localized", "value": {"ru": "Title", "en": "Name"}},
    {"name": "description",  "type": "wysiwyg",  "value": "<p>Description...</p>"},
    {"name": "price",        "type": "numeric",  "value": 1500.00},
    {"name": "weight",       "type": "numeric",  "value": 0.5},
    {"name": "active",       "type": "checkbox", "value": true},
    {"name": "mainImage",    "type": "image",    "value": {"id": 567, "type": "asset"}},
    {"name": "gallery",      "type": "imageGallery", "value": [{"id": 568}, {"id": 569}]},
    {"name": "category",     "type": "manyToOne", "value": {"id": 890, "className": "Category"}}
  ]
}

Mapping and Synchronization in Bitrix

The agent reads objects from Pimcore page by page and updates/creates infoblock elements:

function syncPimcoreProductsAgent(): string
{
    $client   = new PimcoreClient(PIMCORE_URL, PIMCORE_API_KEY);
    $offset   = (int)\Bitrix\Main\Config\Option::get('pimcore_sync', 'offset', 0);
    $limit    = 50;
    $products = $client->getObjects('Product', $offset, $limit, '{"active":true}');

    if (empty($products)) {
        // Reset for next full cycle
        \Bitrix\Main\Config\Option::set('pimcore_sync', 'offset', 0);
        return __FUNCTION__ . '();';
    }

    foreach ($products as $pimProduct) {
        importPimcoreProduct($pimProduct, $client);
    }

    \Bitrix\Main\Config\Option::set('pimcore_sync', 'offset', $offset + $limit);
    return __FUNCTION__ . '();';
}

function importPimcoreProduct(array $product, PimcoreClient $client): void
{
    // Extract field value from Data Object structure
    $getField = static function (array $elements, string $name) {
        foreach ($elements as $el) {
            if ($el['name'] === $name) {
                return $el['value'];
            }
        }
        return null;
    };

    $elements = $product['elements'];
    $sku      = $getField($elements, 'sku');
    $nameRu   = $getField($elements, 'name')['ru'] ?? '';
    $descRu   = $getField($elements, 'description') ?? '';
    $active   = $getField($elements, 'active') ? 'Y' : 'N';

    // Find by article
    $existing = CIBlockElement::GetList(
        [],
        ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'PROPERTY_CML2_ARTICLE' => $sku]
    )->Fetch();

    $fields = [
        'IBLOCK_ID'   => CATALOG_IBLOCK_ID,
        'ACTIVE'      => $active,
        'NAME'        => $nameRu,
        'DETAIL_TEXT' => $descRu,
        'DETAIL_TEXT_TYPE' => 'html',
    ];

    $props = ['CML2_ARTICLE' => $sku];

    // Price
    $price = $getField($elements, 'price');

    $iblockEl = new CIBlockElement();

    if ($existing) {
        $productId = $existing['ID'];
        $iblockEl->Update($productId, $fields);
        CIBlockElement::SetPropertyValuesEx($productId, CATALOG_IBLOCK_ID, $props);
    } else {
        $productId = $iblockEl->Add($fields);
        if ($productId) {
            CIBlockElement::SetPropertyValuesEx($productId, CATALOG_IBLOCK_ID, $props);
        }
    }

    if ($productId && $price !== null) {
        updateCatalogPrice($productId, (float)$price);
    }

    // Main image
    $mainImage = $getField($elements, 'mainImage');
    if ($productId && isset($mainImage['id'])) {
        importPimcoreAsset($productId, (int)$mainImage['id'], $client, 'DETAIL_PICTURE');
    }
}

function importPimcoreAsset(
    int $productId,
    int $assetId,
    PimcoreClient $client,
    string $field
): void {
    $cacheKey = "pimcore_asset_{$assetId}";
    $cache    = \Bitrix\Main\Data\Cache::createInstance();
    if ($cache->initCache(86400 * 30, $cacheKey, '/pimcore/assets')) {
        $bitrixFileId = $cache->getVars()['file_id'];
    } else {
        $content    = $client->getAsset($assetId);
        $tmpPath    = sys_get_temp_dir() . "/pim_{$assetId}.jpg";
        file_put_contents($tmpPath, $content);

        $bitrixFile   = \CFile::MakeFileArray($tmpPath);
        $bitrixFileId = \CFile::SaveFile($bitrixFile, 'iblock');
        unlink($tmpPath);

        $cache->startDataCache(86400 * 30, $cacheKey, '/pimcore/assets');
        $cache->endDataCache(['file_id' => $bitrixFileId]);
    }

    if ($field === 'DETAIL_PICTURE') {
        \CIBlockElement::Update($productId, ['DETAIL_PICTURE' => $bitrixFileId]);
    } else {
        \CIBlockElement::SetPropertyValues($productId, CATALOG_IBLOCK_ID,
            $bitrixFileId, 'MORE_PHOTO');
    }
}

Pimcore Webhooks for Real-time Updates

Pimcore supports webhooks when Data Objects change. Configure in Pimcore → Settings → Webhooks:

  • Event: pimcore.dataobject.postUpdate
  • URL: https://bitrix-site.ru/local/pimcore-webhook.php
  • Secret for signature verification

Handler on Bitrix side:

// /local/pimcore-webhook.php
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PIMCORE_SIGNATURE'] ?? '';

if (!verifyPimcoreSignature($payload, $signature, PIMCORE_WEBHOOK_SECRET)) {
    http_response_code(403);
    die('Invalid signature');
}

$data = json_decode($payload, true);
if ($data['className'] === 'Product') {
    $client  = new PimcoreClient(PIMCORE_URL, PIMCORE_API_KEY);
    $product = $client->getObjects('Product', 0, 1, json_encode(['id' => $data['id']]));
    if (!empty($product[0])) {
        importPimcoreProduct($product[0], $client);
    }
}

http_response_code(200);

Implementation Timeline

Scope Components Timeline
1,000–10,000 SKU, basic mapping Client + page loading agent 1–2 weeks
50,000+ SKU + media files + categories Queue + asset cache + webhooks 3–5 weeks
Bilingual content + product variants Localized fields + trade offers add 1–2 weeks