Developing a custom PIM system for 1C-Bitrix

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

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_value and 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.