Development of a 1C-Bitrix data import module

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
    1173
  • 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
    745
  • 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

Development of Data Import Module for 1C-Bitrix

Importing data to 1C-Bitrix is not just "uploading CSV". It's transforming data from a foreign format into Bitrix structures with consideration for infoblock, merchandise catalog, CRM, users. The built-in import exists only for the catalog via CommerceML and YML; for everything else, a separate module is written.

What the Standard Provides and Where It Stops

The standard catalog module supports import via CSaleImport and the CDataExchangeXML class for CommerceML 2.x. Works acceptably for simple catalogs. Problems start with:

  • arbitrary formats (Excel, JSON, non-standard XML schemas)
  • importing to multiple infoblocks simultaneously
  • updating existing records with field merge logic
  • importing users, orders, CRM entities
  • large files (10,000+ rows) without timeouts

Module Architecture

The module vendor.importer is built around three abstractions: Reader (reads source), Transformer (transforms data), Writer (writes to Bitrix).

SourceFile → Reader → RawRow[] → Transformer → MappedRow[] → Writer → Bitrix entities

ReaderInterface:

interface ReaderInterface
{
    public function open(string $filePath): void;
    public function readNext(): ?array;    // null = end of file
    public function getHeaders(): array;
    public function close(): void;
}

Implementations: CsvReader, XlsxReader (via PhpSpreadsheet), XmlReader (via XMLReader for streaming large files), JsonReader.

Field Mapping

The mapping configuration is stored in b_vendor_importer_mapping:

{
  "source": "csv",
  "target": "catalog_product",
  "iblock_id": 5,
  "fields": {
    "article": "PROPERTY_ARTICLE",
    "name": "NAME",
    "price": "CATALOG_PRICE_1",
    "stock": "CATALOG_STORE_PRODUCT_AMOUNT"
  },
  "key_field": "article",
  "update_strategy": "merge"
}

key_field determines the field for identifying an existing record (article, external ID, email). update_strategy:

  • merge — updates only fields from mapping, doesn't touch others
  • replace — full record replacement
  • skip — skip existing, only new

Streaming Large Files

You can't process 50,000 rows in a single HTTP request. We use a session approach with an agent:

// Step 1: upload file, create job
$job = ImportJobTable::add([
    'FILE_PATH'  => $uploadedPath,
    'MAPPING_ID' => $mappingId,
    'STATUS'     => 'pending',
    'TOTAL'      => 0,
    'PROCESSED'  => 0,
]);

// Step 2: agent reads in batches
public static function processChunk(int $jobId, int $offset, int $limit = 100): string
{
    $job = ImportJobTable::getById($jobId)->fetch();
    $reader = ReaderFactory::create($job['FILE_PATH']);
    $reader->open($job['FILE_PATH']);
    // skip offset rows, read limit rows
    // ...process, update PROCESSED
}

The agent is called every minute, processes the next batch, updates the counter. Progress is visible in the admin interface in real time.

Writer: Writing to Iblock

class IblockElementWriter implements WriterInterface
{
    public function write(MappedRow $row): WriteResult
    {
        $existing = $this->findByKey($row->getKeyValue());

        if ($existing) {
            $result = \CIBlockElement::Update($existing['ID'], $row->toIblockArray());
        } else {
            $element = new \CIBlockElement();
            $result = $element->Add($row->toIblockArray());
        }

        return new WriteResult($result !== false, $existing ? 'updated' : 'created');
    }
}

Similar Writer classes: CrmLeadWriter (via \Bitrix\Crm\LeadTable), SaleOrderWriter (via \Bitrix\Sale\Order::create()), UserWriter (via \CUser::Add/Update).

Data Validation

Before writing, each row passes validation. Rules are described declaratively:

$rules = [
    'EMAIL'  => ['required', 'email'],
    'PRICE'  => ['required', 'numeric', 'min:0'],
    'NAME'   => ['required', 'max:255'],
    'STATUS' => ['in:active,inactive,pending'],
];

Rows with errors are not written, they are logged in b_vendor_importer_error with row number, field, and reason. After import, an error report is available with the option to download CSV of "problematic rows".

Notifications and Logging

Upon completion, the module sends an email notification to the initiator via \Bitrix\Main\Mail\Event::send(). The notification includes: total rows, created/updated/errors, link to the log.

The log is stored in b_vendor_importer_log: job_id, line_number, action (created/updated/skipped/error), entity_id, message, created_at.

Development Timeline

Stage Duration
Architecture, interfaces, installer 1 day
Readers: CSV, XLSX, XML, JSON 2 days
Mapping, transformer, configurator 2 days
Writers for specific Bitrix entities 3 days
Streaming processing, agents 2 days
Validation, logging, error report 1 day
Admin interface, testing 2 days

Total: 13 working days. Non-standard source formats or complex transformation logic (deduplication, enrichment from external APIs) are estimated separately.