Developing a Custom PIM System for 1C-Bitrix
Commercial PIM systems (Akeneo, Pimcore, Salsify) solve content management tasks, but create dependency on third-party products and add architectural complexity. A custom PIM based on Bitrix — is when Bitrix itself becomes the master system for product data: with custom administrative interface, attribute validation logic, and ability to syndicate to multiple channels.
When Custom PIM is Justified
Custom PIM is justified when:
- Team works only within Bitrix ecosystem and doesn't want to learn new system
- Budget doesn't allow Akeneo Enterprise license ($25,000+/year)
- Requirements are specific enough that ready-made PIM would require extensive customization anyway
- Catalog has 50,000–100,000 SKU (for larger volumes, consider specialized PIM)
Data Architecture
Standard Bitrix infoblock stores properties in b_iblock_element_property using EAV model. With many attributes (50+) and high traffic this creates performance issues. For custom PIM, use hybrid approach:
Fixed attributes (title, description, images) — in infoblock fields and properties with MULTIPLE = 'N'.
Dynamic attributes (category-specific: for phones — RAM/ROM, for clothing — fabric composition) — in custom tables:
CREATE TABLE b_pim_attribute (
ID INT AUTO_INCREMENT PRIMARY KEY,
IBLOCK_SECTION_ID INT, -- which category attribute belongs to
CODE VARCHAR(100) NOT NULL,
NAME VARCHAR(255) NOT NULL,
TYPE ENUM('string','number','boolean','list','multilist') DEFAULT 'string',
IS_REQUIRED TINYINT(1) DEFAULT 0,
IS_FILTERABLE TINYINT(1) DEFAULT 0,
SORT INT DEFAULT 100,
INDEX idx_section (IBLOCK_SECTION_ID)
);
CREATE TABLE b_pim_attribute_value (
ID INT AUTO_INCREMENT PRIMARY KEY,
PRODUCT_ID INT NOT NULL, -- infoblock element ID
ATTRIBUTE_ID INT NOT NULL,
VALUE_STRING VARCHAR(1000),
VALUE_NUMBER DECIMAL(15,4),
VALUE_BOOLEAN TINYINT(1),
INDEX idx_product_attr (PRODUCT_ID, ATTRIBUTE_ID)
);
CREATE TABLE b_pim_attribute_option (
ID INT AUTO_INCREMENT PRIMARY KEY,
ATTRIBUTE_ID INT NOT NULL,
VALUE VARCHAR(500) NOT NULL,
SORT INT DEFAULT 100
);
This approach allows setting attributes at category level without changing infoblock schema.
Administrative Interface for Attribute Editing
Custom administrative section based on bitrix:main.ui.grid component:
// /local/modules/company.pim/admin/attributes.php
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin_before.php';
$APPLICATION->SetTitle('PIM: Attribute Management');
// Attribute list for selected category
$sectionId = (int)$_GET['section_id'];
$connection = \Bitrix\Main\Application::getConnection();
$attributes = $connection->query(
"SELECT * FROM b_pim_attribute WHERE IBLOCK_SECTION_ID = {$sectionId} ORDER BY SORT"
);
Interface allows manager to:
- Add attributes to category (without DB schema changes)
- Set type, required status, filterability
- Sort attributes via drag-and-drop
- Copy attribute set to child category
Product Edit Form with Dynamic Attributes
In product editor (sale.admin.order.edit or custom page) display and save attributes for current category:
function renderPimFields(int $productId, int $sectionId): string
{
$connection = \Bitrix\Main\Application::getConnection();
$attributes = $connection->query(
"SELECT a.*, v.VALUE_STRING, v.VALUE_NUMBER, v.VALUE_BOOLEAN
FROM b_pim_attribute a
LEFT JOIN b_pim_attribute_value v
ON v.ATTRIBUTE_ID = a.ID AND v.PRODUCT_ID = {$productId}
WHERE a.IBLOCK_SECTION_ID = {$sectionId}
ORDER BY a.SORT"
);
$html = '<table class="pim-attributes">';
while ($attr = $attributes->fetch()) {
$html .= '<tr>';
$html .= '<td><label>' . htmlspecialchars($attr['NAME'])
. ($attr['IS_REQUIRED'] ? ' <span class="req">*</span>' : '')
. '</label></td>';
$html .= '<td>' . renderAttributeInput($attr) . '</td>';
$html .= '</tr>';
}
$html .= '</table>';
return $html;
}
function savePimFields(int $productId, array $postData): void
{
$connection = \Bitrix\Main\Application::getConnection();
foreach ($postData as $attrId => $value) {
$attrId = (int)$attrId;
$existing = $connection->query(
"SELECT ID FROM b_pim_attribute_value
WHERE PRODUCT_ID = {$productId} AND ATTRIBUTE_ID = {$attrId}"
)->fetch();
$attr = $connection->query(
"SELECT TYPE FROM b_pim_attribute WHERE ID = {$attrId}"
)->fetch();
$field = match($attr['TYPE']) {
'number' => 'VALUE_NUMBER',
'boolean' => 'VALUE_BOOLEAN',
default => 'VALUE_STRING',
};
$safeVal = $connection->getSqlHelper()->forSql($value);
if ($existing) {
$connection->query(
"UPDATE b_pim_attribute_value
SET {$field} = '{$safeVal}'
WHERE ID = {$existing['ID']}"
);
} else {
$connection->query(
"INSERT INTO b_pim_attribute_value
(PRODUCT_ID, ATTRIBUTE_ID, {$field})
VALUES ({$productId}, {$attrId}, '{$safeVal}')"
);
}
}
}
Attribute Validation
Required attributes are checked when saving product:
function validatePimAttributes(int $productId, int $sectionId): array
{
$errors = [];
$connection = \Bitrix\Main\Application::getConnection();
$required = $connection->query(
"SELECT a.ID, a.NAME FROM b_pim_attribute a
LEFT JOIN b_pim_attribute_value v
ON v.ATTRIBUTE_ID = a.ID AND v.PRODUCT_ID = {$productId}
WHERE a.IBLOCK_SECTION_ID = {$sectionId}
AND a.IS_REQUIRED = 1
AND v.ID IS NULL"
);
while ($row = $required->fetch()) {
$errors[] = 'Required attribute not filled: ' . $row['NAME'];
}
return $errors;
}
Syndication to Multiple Channels
Custom PIM allows exporting data to different formats for different channels:
-
Yandex.Market YML — feed generator reads
b_pim_attribute_valueand maps to standard YML elements - OZON / Wildberries — JSON templates for each marketplace
-
Google Merchant — XML feed with attributes
g:brand,g:gtin, etc.
function buildYmlOffer(int $productId): array
{
$connection = \Bitrix\Main\Application::getConnection();
$values = $connection->query(
"SELECT a.CODE, v.VALUE_STRING, v.VALUE_NUMBER
FROM b_pim_attribute_value v
JOIN b_pim_attribute a ON a.ID = v.ATTRIBUTE_ID
WHERE v.PRODUCT_ID = {$productId}"
);
$params = [];
while ($row = $values->fetch()) {
$params[$row['CODE']] = $row['VALUE_STRING'] ?? $row['VALUE_NUMBER'];
}
return $params; // used when generating YML
}
Implementation Timeline
| Scope | Components | Timeline |
|---|---|---|
| Basic PIM (dynamic attributes, edit form) | Tables + form + save | 2–3 weeks |
| PIM with validation, sort UI, attribute copying | Full admin UI + JS-sorting | 4–5 weeks |
| PIM + syndication (YML, marketplaces) + bulk edit | Exporters + bulk edit table | 6–8 weeks |
Custom PIM is the right choice for teams already working in Bitrix. Main risk — excessive complexity: need to lock down requirements before starting development and not try to reproduce Akeneo inside Bitrix.







