1C-Bitrix Data Export Module Development
Data export from Bitrix looks simple at first glance: SELECT from the required tables and generate a file. In practice it is far more complex: data is distributed across dozens of tables, JOINs must account for access rights, the file must be generated asynchronously without blocking the interface, and formats may vary depending on the recipient.
Typical Scenarios
- Catalogue export for marketplaces (Ozon, Wildberries) in a specific XML or JSON format
- Order export to 1C:Accounting via XML or CSV
- Regular CRM lead export to Google Sheets or a BI system
- Transferring user data to an ESP (UniSender, SendGrid)
- Backup data export for migration to another platform
Each scenario requires its own field mapping and format. A unified module lets you manage all exports in one place.
Module Structure
The vendor.exporter module with three layers:
- Collector — gathers data from the source (ORM queries to Bitrix tables)
- Transformer — converts raw data into the required structure
- Formatter — produces the final file in the required format
Module tables:
-
b_vendor_exporter_profile— export profiles: name, source_config (JSON), format, schedule, last_run, last_file -
b_vendor_exporter_job— export jobs: profile_id, status, started_at, finished_at, file_path, rows_count, error -
b_vendor_exporter_field— field mapping for a profile: profile_id, source_field, target_field, transform_rule
Data Collectors
class CatalogProductCollector implements CollectorInterface
{
public function collect(array $filter, array $select): \Generator
{
$query = \Bitrix\Iblock\Elements\ElementTable::query()
->setFilter($filter)
->setSelect($select);
foreach ($query->fetchAll() as $row) {
// JOIN with prices
$prices = \Bitrix\Catalog\PriceTable::getList([
'filter' => ['=PRODUCT_ID' => $row['ID']],
'select' => ['PRICE', 'CURRENCY', 'CATALOG_GROUP_NAME'],
])->fetchAll();
$row['PRICES'] = $prices;
yield $row;
}
}
}
Using a Generator instead of an array is critical when exporting 100,000+ records — data does not accumulate in memory.
Formatters
CSV formatter with delimiter and encoding support:
class CsvFormatter implements FormatterInterface
{
public function format(\Generator $data, array $columns, string $outputPath): void
{
$handle = fopen($outputPath, 'w');
fputs($handle, "\xEF\xBB\xBF"); // UTF-8 BOM for Excel
fputcsv($handle, array_column($columns, 'label'), $this->delimiter);
foreach ($data as $row) {
fputcsv($handle, $this->extractValues($row, $columns), $this->delimiter);
}
fclose($handle);
}
}
XML formatter for arbitrary schemas — configured via an XSD or XSLT template. XLSX formatter via PhpSpreadsheet with cell formatting support. JSON formatter with configurable structure: flat array or nested objects.
Asynchronous Generation
The file is not generated synchronously during an HTTP request. The administrator clicks "Run export", a job is created in b_vendor_exporter_job with status queued. The ExportAgent::run() agent fires every minute:
public static function run(): string
{
$job = ExportJobTable::getList([
'filter' => ['=STATUS' => 'queued'],
'order' => ['CREATED_AT' => 'ASC'],
'limit' => 1,
])->fetch();
if (!$job) return __CLASS__ . '::run();';
ExportJobTable::update($job['ID'], ['STATUS' => 'running', 'STARTED_AT' => new DateTime()]);
try {
$profile = ExportProfileTable::getById($job['PROFILE_ID'])->fetch();
$service = new ExportService($profile);
$filePath = $service->execute();
ExportJobTable::update($job['ID'], [
'STATUS' => 'done',
'FILE_PATH' => $filePath,
'FINISHED_AT' => new DateTime(),
]);
} catch (\Throwable $e) {
ExportJobTable::update($job['ID'], ['STATUS' => 'failed', 'ERROR' => $e->getMessage()]);
}
return __CLASS__ . '::run();';
}
Scheduling and Auto-delivery
Profiles support cron scheduling. After generation the file can be:
- downloaded manually from the administrative interface
- sent by email (via
\Bitrix\Main\Mail\Event::send()) - transferred via FTP/SFTP (
\Bitrix\Main\IO+ PHP ftp functions) - published at a URL (file placed in
/upload/vendor_exporter/, link recorded in the profile) - sent as a POST request to an external API (webhook with file in multipart/form-data)
Filtering and Partial Export
A base filter is configured in the profile. When running manually, the administrator can extend the filter via a form: date range, statuses, specific IDs. The filter is passed to the collector via filter_override:
$collector->collect(
array_merge($profile->getBaseFilter(), $job->getFilterOverride()),
$profile->getSelect()
);
Security and Permissions
Export of personal data (users, orders) is available only to groups with explicitly granted rights in the module settings. The following is logged: who ran the export, when, how many records, the file path. The log is stored in b_vendor_exporter_audit.
Development Timeline
| Stage | Duration |
|---|---|
| Architecture, interfaces, tables | 1 day |
| Collectors for required Bitrix entities | 2 days |
| Formatters (CSV, XML, XLSX, JSON) | 2 days |
| Asynchronous generation, agent | 1 day |
| Scheduling, auto-delivery (email, FTP) | 2 days |
| Administrative interface | 2 days |
| Filtering, permissions, audit log | 1 day |
| Testing with large data volumes | 1 day |
Total: 12 working days for a standard set of formats and sources. Marketplace-specific formats (especially those with mandatory certification fields) — +1–2 days each.







