Bulk Product Photo Upload Configuration in 1C-Bitrix
A catalog of 20,000 items where each product needs its main photo replaced and a gallery of 5–8 images added. Through the admin interface, that's several person-weeks of manual work. In practice, the task is solved in a few hours using the Bitrix API and batch processing.
Where Photos Are Stored in Bitrix
Product image files are stored in the b_file table. Each file has its own ID. The iblock (catalog) references them via properties:
-
PREVIEW_PICTURE— announcement image, stored inb_iblock_element.PREVIEW_PICTURE(FK tob_file.ID) -
DETAIL_PICTURE— detail image,b_iblock_element.DETAIL_PICTURE - A property of type "File/Image" —
b_iblock_element_property.VALUEreferencingb_file.ID
Multiple images (gallery) — via a property of type "Image" with the "Multiple" flag. For trade offers — the same fields in the context of the trade offer element.
Preparing Files
Images must be prepared in advance: directory structure by SKU, consistent naming. A convenient scheme:
/import/images/
sku_001/
main.jpg
2.jpg
3.jpg
sku_002/
main.jpg
If files come from 1C — names typically match the XML_ID of the element. If from a photo studio — a mapping file is needed (CSV: SKU → file names).
Uploading via CFile and CIBlockElement
The basic approach: read a CSV with the mapping, for each row upload the file via CFile::SaveFile() and update the element.
$csvRows = parseCsv('/import/mapping.csv'); // [['xml_id' => 'sku_001', 'files' => ['main.jpg', '2.jpg']]]
foreach ($csvRows as $row) {
// Find element by XML_ID
$element = \Bitrix\Iblock\ElementTable::getList([
'filter' => ['XML_ID' => $row['xml_id'], 'IBLOCK_ID' => CATALOG_IBLOCK_ID],
'select' => ['ID'],
])->fetch();
if (!$element) continue;
$imageDir = '/import/images/' . $row['xml_id'] . '/';
$files = [];
foreach ($row['files'] as $i => $filename) {
$filePath = $imageDir . $filename;
if (!file_exists($filePath)) continue;
$fileId = \CFile::SaveFile([
'name' => $filename,
'type' => mime_content_type($filePath),
'tmp_name' => $filePath,
'error' => 0,
'size' => filesize($filePath),
], 'iblock');
if ($i === 0) {
// First file — main image
\CIBlockElement::Update($element['ID'], [
'PREVIEW_PICTURE' => \CFile::MakeFileArray($filePath),
'DETAIL_PICTURE' => \CFile::MakeFileArray($filePath),
]);
} else {
$files[] = ['VALUE' => \CFile::MakeFileArray($filePath)];
}
}
// Multiple gallery property
if (!empty($files)) {
\CIBlockElement::SetPropertyValues($element['ID'], CATALOG_IBLOCK_ID, $files, 'MORE_PHOTO');
}
}
CFile::MakeFileArray() does not copy the file — it is simply a descriptor array. CFile::SaveFile() performs the actual save and writes to b_file.
Performance at High Volumes
On a catalog of 20,000+ items, a direct loop will run for 2–4 hours and may fail due to a timeout or memory limit. A few rules:
Split into batches. Process 200–500 elements per iteration, saving progress to a file or table.
Disable unnecessary event handlers. During bulk updates, the OnBeforeIBlockElementUpdate and OnAfterIBlockElementUpdate events can trigger a chain of heavy operations (price recalculation, cache invalidation, search index update). Temporarily disable agents and events if they are not needed during import:
define('BX_DONT_PROCESS_EVENTS', true); // only if you fully understand the consequences
// OR selectively:
\Bitrix\Main\EventManager::getInstance()->removeEventHandler('iblock', 'OnAfterIBlockElementUpdate', $handlerId);
Use CLI scripts. Run via php -f import_images.php — no web request time limits. Set set_time_limit(0) and ini_set('memory_limit', '512M').
Clear cache after completion. After bulk upload, reset the iblock cache: \Bitrix\Iblock\InformationBlock::cleanTagCache($iblockId). Do not do this inside the loop — only once at the end.
Error Handling and Logging
$log = fopen('/var/log/image_import.log', 'a');
foreach ($csvRows as $row) {
try {
// ... processing
fwrite($log, date('Y-m-d H:i:s') . " OK: {$row['xml_id']}\n");
} catch (\Throwable $e) {
fwrite($log, date('Y-m-d H:i:s') . " ERR: {$row['xml_id']} — {$e->getMessage()}\n");
}
}
Common errors: file not found, invalid MIME type, duplicate in b_file (Bitrix checks by hash — re-uploading the same file will return the existing ID).
Thumbnail Generation
After upload, Bitrix generates thumbnails lazily — on first access via a component. To warm the cache immediately:
\CFile::ResizeImageGet($fileId, ['width' => 400, 'height' => 400], BX_RESIZE_IMAGE_PROPORTIONAL, true);
Or via a CLI utility if ImageMagick is configured on the server.
Estimated Timelines
| Catalog Size | Estimated Time |
|---|---|
| Up to 1,000 products | 1–2 hours (setup + upload) |
| 1,000–10,000 products | 4–8 hours |
| 10,000–50,000 products | 1–2 days (including testing and cache warm-up) |







