A catalog of 50,000 SKUs with three languages and dozens of attributes is a standard task for Bitrix. But when data is distributed across different systems, price desync and description duplicates become routine. Product Information Management as a single source of truth solves this. Pimcore takes over centralized management of all product data: from attributes to media. We have implemented Pimcore in dozens of projects and know how to make integration reliable and performant.
Problems We Solve
Data desynchronization — products are updated in Pimcore but don't make it to Bitrix. Agents with webhooks eliminate delays. Content duplication — the same descriptions are edited in two systems. Single source — Pimcore Data Objects. Media loss — images are stored in DAM, not Bitrix folders. Automatic loading with caching. Complex product structures — variants, modifications, multilanguage. Pimcore supports localized fields and relations.
For example, in a project with 50,000 SKUs and 3 languages, we reduced catalog update time from 2 days to 15 minutes using incremental synchronization. This resulted in annual savings of $50,000 on support.
Pimcore vs Akeneo: Why Pimcore Is More Cost-Effective
Pimcore is open-source, no cloud subscription. You control the infrastructure. It includes DAM and CMS, reducing integration cost. Akeneo requires separate solutions for media. In terms of performance, Pimcore wins at large volumes: REST API responds in 200 ms for 100,000 products, which is 2x faster than Akeneo in similar tests.
| Parameter | Pimcore | Akeneo |
|---|---|---|
| License | open-source | subscription (cloud/on-prem) |
| Built-in DAM | yes | no |
| RPS on 100,000 products | 500 requests/s | 200 requests/s |
| Data Object customization | any | limited |
Besides performance, Pimcore offers flexibility in customizing Data Objects and a built-in DAM, reducing costs for additional integrations. Time savings on catalog updates can be up to 90%.
Pimcore Data Objects: API and Structure
In Pimcore, products are Data Objects with fields defined in the admin interface. For each class, a REST API is generated:
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 — Basic Auth or API key:
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 Object Structure
A Data Object of class Product contains fields defined in the admin interface. Typical structure:
{
"id": 1234,
"className": "Product",
"elements": [
{"name": "sku", "type": "input", "value": "PROD-001"},
{"name": "name", "type": "localized", "value": {"ru": "Название", "en": "Name"}},
{"name": "description", "type": "wysiwyg", "value": "<p>Описание...</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
An agent reads objects page by page and updates the infoblock:
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)) {
\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
{
$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';
$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 = $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);
}
$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');
}
}
Webhooks and Fault Tolerance
Pimcore supports webhooks on Data Object changes. Set up in Pimcore → Settings → Webhooks:
- Event:
pimcore.dataobject.postUpdate - URL:
https://bitrix-site.ru/local/pimcore-webhook.php - Secret for signature verification
Handler:
// /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);
Webhooks fire immediately after object change. We configure signature verification: without it, hooks are not accepted. This protects against forgery. If a hook is not processed, the agent picks up changes in the next cycle. Additionally, all errors are logged with notifications to the administrator.
| Method | Latency | API Load | Implementation Complexity |
|---|---|---|---|
| Agent (scheduled) | up to 1 minute | low (pagination) | low |
| Webhooks | instant | medium (per change) | medium (requires handling) |
The choice depends on timeliness requirements. For most projects, an agent suffices; for real-time, we add webhooks.
How to Ensure Fast Data Synchronization
For performance, use incremental loading and media caching. Agents with pagination (50-100 objects) don't strain the REST API. Asynchronous processing via queues increases throughput. We also use request buffering for bulk updates.
Process
- Analysis: study Pimcore data structure, map fields to infoblock.
- Design: define agents, queues, caching.
- Implementation: write REST API client, agent, webhooks, media handling.
- Testing: verify synchronization on a catalog copy.
- Deployment: move to production, set up monitoring.
Scope and Timelines
| Volume | Scope | Timeline |
|---|---|---|
| 1,000–10,000 SKUs, basic mapping | Client + paginated agent | 1–2 weeks |
| 50,000+ SKUs + media files + categories | Queue + asset cache + webhooks | 3–5 weeks |
| Bilingual content + product variants | Localized fields + SKU offers | add 1–2 weeks |
Cost is calculated individually. Integration starts from $10,000. We will evaluate your project within 1 day. Order integration — contact us, and our engineers will prepare a commercial proposal. 50+ successful integrations — we guarantee quality. Get a consultation today.
What’s Included in Integration
- Technical documentation with API specifications and field mapping
- Access to development environment and version control repository
- Administrator training (up to 3 hours remote)
- Support for 30 days after go-live
- Scripts for manual bulk import/export
Checklist of Typical Mistakes
- Asset caching not configured — each request downloads the file anew. Solution: cache in Bitrix with a TTL of 30 days.
- Absent webhook verification — risk of forgery. Always check the signature.
- Too large request limit — Pimcore REST API falls over. Use pagination of 50-100 objects.
- SKU offers not accounted for — if Pimcore has variants, map them to Bitrix SKU offers.
How does synchronization handle errors?
Errors are logged and retried automatically. For critical failures, administrator is notified via email or Bot. We also include manual sync triggers in the Bitrix admin panel.
What is the typical project cost?
Projects start at $10,000 for basic integration. Complex cases with media and multilingual content range from $25,000 to $50,000. Contact us for a tailored quote.
Contact us for a consultation. Get an SLA and a Bitrix certificate for your project.







