Custom Import/Export Module Development for 1C-Bitrix
Standard exchange via CommerceML covers typical tasks with 1C: catalog, orders, balances. But when data comes in a non-standard format – XML from a supplier, JSON from a CRM, XLSX reports – performance drops and mapping becomes a quest. A custom import/export module solves this: it works directly with any source, uses stream processing for large volumes, and supports bidirectional sync with collision control. We develop such modules turnkey: from schema analysis to deployment and admin training. Our team has been doing custom development for 10+ years and has successfully implemented 40+ integrations for retail, logistics, and manufacturing. We'll evaluate your project within one day – contact us for a consultation.
A typical scenario: you have an external warehouse or ERP system that outputs data in CommerceML format. But CommerceML is a heavy XML that takes hours to process volumes over 10,000 items per load. A custom module parses data with its own parser, using XMLReader for streaming, and can process up to 1,000 elements per second. The difference is 3–5 times faster than standard exchange. Additionally, we implement batch processing with Bitrix agents, eliminating web request timeouts.
When CommerceML Falls Short
CommerceML is a universal protocol, but it's not always flexible. If your data source is a JSON API of a third-party service, a binary XLSX with thousands of rows, or requires complex field transformations (merging, deduplication, linking to reference tables), standard exchange is inefficient. A custom module can handle arbitrary formats: XML (any schema), CSV, XLSX, JSON, and also fetch files via FTP/SFTP. For volumes over 100,000 records, chunked loading is mandatory – we embed it into the module core.
How Bidirectional Synchronization Works
The hardest part is avoiding collisions when changes occur simultaneously in both systems. We use timestamp-based sync with a sync_hash field. On export, we record the hash of the record state. On the next import: if the hash changed in the source – we update Bitrix; if it changed in Bitrix (user edited) – we send changes back to the source. If both changed – conflict, logged for manual resolution.
SQL for adding sync_hash field
ALTER TABLE b_iblock_element ADD COLUMN sync_hash VARCHAR(32);
ALTER TABLE b_iblock_element ADD COLUMN synced_at DATETIME;
Custom Module Architecture
A Bitrix module is a directory in /local/modules/{vendor}.{modulename}/ registered via RegisterModule. Standard structure:
local/modules/company.import/
├── install/
│ ├── index.php # InstallDB(), UnInstallDB(), DoInstall()
│ └── db/mysql/install.sql
├── lib/
│ ├── Importer.php # main logic
│ ├── Parser/
│ │ ├── XmlParser.php
│ │ └── CsvParser.php
│ └── Queue/
│ └── ImportQueue.php
├── admin/
│ └── import_settings.php # administrative interface
├── include.php
└── .settings.php
Import from Custom XML Source
Example parser implementation for a custom schema:
namespace Company\Import;
use Bitrix\Main\Loader;
use Bitrix\Catalog\ProductTable;
class Importer {
private \SimpleXMLElement $xml;
public function __construct(string $filePath) {
Loader::includeModule('iblock');
Loader::includeModule('catalog');
$this->xml = simplexml_load_file($filePath);
}
public function run(): array {
$stats = ['created' => 0, 'updated' => 0, 'errors' => 0];
foreach ($this->xml->products->product as $product) {
try {
$this->processProduct($product, $stats);
} catch (\Throwable $e) {
\Bitrix\Main\Diag\Debug::writeToFile($e->getMessage(), 'IMPORT ERROR', '/bitrix/modules/company.import/error.log');
$stats['errors']++;
}
}
return $stats;
}
private function processProduct(\SimpleXMLElement $p, array &$stats): void {
$externalId = (string)$p->id;
$existing = $this->findByExternalId($externalId);
$fields = [
'IBLOCK_ID' => IMPORT_IBLOCK_ID,
'NAME' => (string)$p->name,
'CODE' => \CUtil::translit((string)$p->name, 'ru'),
'ACTIVE' => (string)$p->is_active === '1' ? 'Y' : 'N',
'PROPERTY_VALUES' => [
'EXTERNAL_ID' => $externalId,
'VENDOR_CODE' => (string)$p->sku,
'DESCRIPTION' => (string)$p->description,
],
];
if ($existing) {
\CIBlockElement::Update($existing, $fields);
$stats['updated']++;
} else {
$el = new \CIBlockElement();
$newId = $el->Add($fields);
if (!$newId) throw new \RuntimeException($el->LAST_ERROR);
$stats['created']++;
}
// Update price and stock
\CPrice::SetBasePrice($newId ?? $existing, (float)$p->price, 'RUB');
\CCatalogProduct::Update($newId ?? $existing, ['QUANTITY' => (int)$p->stock]);
}
}
Batch Processing and Agents
For 100,000+ records, a direct web request is impossible – timeout. We use a Bitrix agent that saves progress in the database. The agent runs every minute and processes the next chunk:
class ImportQueue {
public static function processChunk(int $jobId, int $offset, int $limit = 500): array {
$job = ImportJobTable::getById($jobId)->fetch();
// ... read $limit rows starting from $offset
// ... process
// ... update progress in DB
return ['processed' => $count, 'total' => $job['total_rows']];
}
}
function ImportAgent(): string {
$activeJob = getActiveImportJob();
if (!$activeJob) return '';
$result = ImportQueue::processChunk($activeJob['id'], $activeJob['offset']);
if ($activeJob['offset'] + $result['processed'] >= $result['total']) {
markJobComplete($activeJob['id']);
return '';
}
return 'ImportAgent();';
}
Data Export
Export to custom format for an external system:
class Exporter {
public function exportOrders(\DateTime $from, \DateTime $to): string {
$orders = \Bitrix\Sale\OrderTable::getList([
'filter' => [
'>=DATE_INSERT' => $from->format('d.m.Y H:i:s'),
'<=DATE_INSERT' => $to->format('d.m.Y H:i:s'),
'CANCELED' => 'N',
],
'select' => ['ID', 'ACCOUNT_NUMBER', 'PRICE', 'CURRENCY', 'DATE_INSERT', 'USER_ID'],
])->fetchAll();
$xml = new \XMLWriter();
$xml->openMemory();
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('orders');
foreach ($orders as $order) {
$xml->startElement('order');
$xml->writeElement('id', $order['ID']);
$xml->writeElement('number', $order['ACCOUNT_NUMBER']);
$xml->writeElement('amount', $order['PRICE']);
$xml->writeElement('date', $order['DATE_INSERT']->format(\DateTime::ATOM));
// ... order items
$xml->endElement();
}
$xml->endElement();
return $xml->outputMemory();
}
}
Formats and Sources
| Format/Source | Tools | Notes |
|---|---|---|
| XML (custom) | SimpleXML, XMLReader | XMLReader for files > 100 MB |
| CSV/XLSX | PhpSpreadsheet, fgetcsv | XLSX – binary, requires library |
| JSON REST API | curl, Guzzle | Pagination, rate limiting |
| FTP/SFTP | phpseclib | Automatic file download |
| 1C CommerceML | Built-in Bitrix exchange | Customization via events |
| Google Sheets | Google Sheets API v4 | For small volumes |
How to Speed Up Large Catalog Import?
For volumes over 50,000 items, standard exchange hits PHP execution time and memory limits. A custom module uses asynchronous agents, chunked loading, and query profiling. We add indexes on external IDs and sync date, accelerating duplicate search by 10x. Clients achieve import of 100,000 elements in 2–3 minutes instead of an hour.
Timeline and Cost
Development cost is calculated individually based on mapping complexity, number of formats, and need for bidirectional sync. We'll give an accurate estimate after analyzing your data.
| Stage | Duration |
|---|---|
| Format analysis and field mapping | 1–2 days |
| Parser/exporter development | 3–5 days |
| Batch processing, Bitrix agent | 2–3 days |
| Administrative interface | 2–3 days |
| Bidirectional sync (if needed) | 3–5 days |
| Testing with real data | 2–3 days |
Total: 2–3 weeks for one-way import; 3–4 weeks for bidirectional sync.
What’s Included
- Module source code with comments.
- Module API documentation and setup guide.
- Access to Git repository with change history.
- Admin training (up to 2 hours online).
- 3-month warranty on uninterrupted operation.
Get an engineer consultation – request a preliminary audit of your exchange.CommerceML — exchange protocol







