Content Distribution System Development for 1C-Bitrix
Managing content on a single website is straightforward. The problem starts when you have multiple sites: a network of regional portals, a group of thematic resources, a multilingual project, or a marketplace with storefronts for different channels. Replicating changes manually is labor-intensive and unreliable. A content distribution system automates the propagation of materials from a source to consumers with flexible control: what, where, when, and with what transformations.
Architecture: Master and Child Sites
The typical scheme — one master site (content source) and several children (consumers). On Bitrix, this is solved in several ways depending on the infrastructure:
Bitrix multisite — if all sites share one installation. Iblock elements are linked to sites via b_iblock_site. One element can be active on multiple sites simultaneously. Managed via the IBLOCK_ELEMENT.ACTIVE field and site relationships.
Distributed scheme — separate Bitrix installations. An API layer is needed: the master publishes content via REST API or a message queue, child sites subscribe and receive updates.
Hybrid — CDN for media files, API for structured content, direct DB replication for urgent updates.
Distribution Iblock: Tables and Logic
To track what has been distributed where, a custom table is needed:
CREATE TABLE content_distribution (
ID INT AUTO_INCREMENT PRIMARY KEY,
SOURCE_ELEMENT_ID INT NOT NULL, -- element ID on master
SOURCE_IBLOCK_ID INT NOT NULL,
TARGET_SITE_ID VARCHAR(8) NOT NULL, -- recipient site ID
TARGET_ELEMENT_ID INT, -- ID on child site (NULL before publishing)
STATUS ENUM('pending','published','failed','excluded') DEFAULT 'pending',
PUBLISHED_AT DATETIME,
ERROR TEXT,
INDEX (SOURCE_ELEMENT_ID),
INDEX (TARGET_SITE_ID, STATUS)
);
When an element is published on the master — the OnAfterIBlockElementAdd/Update event triggers distribution. The handler checks rules: which child sites should receive this content type, and writes tasks to the table.
Distribution Rules
A flexible rules system determines what content goes where:
// Distribution rules configuration
$distributionRules = [
[
'source_iblock_id' => 5, // "News" iblock
'target_sites' => ['s2', 's3', 's4'], // All regional sites
'filter' => [ // Only specific categories
'PROPERTY_CATEGORY' => [1, 2], // IDs of "Federal news" sections
],
'transform' => 'NewsTransformer', // Transformation class
'delay' => 0, // Immediately
],
[
'source_iblock_id' => 8, // "Promotions"
'target_sites' => ['s2'], // Only for one region
'filter' => ['PROPERTY_REGION' => 'msk'],
'transform' => null, // No transformation
'delay' => 3600, // 1 hour after publishing on master
],
];
Content Transformation During Distribution
Content is rarely distributed as-is. Typical transformations:
Link adaptation — absolute internal links must be replaced with links for the child site:
$content = preg_replace(
'|https://master-site\.ru/([^"\']+)|',
'https://regional-site.ru/$1',
$sourceContent
);
Translation — for multilingual projects. Integration with Google Translate API or DeepL for automatic translation, followed by manual proofreading:
$translated = $translationService->translate(
$element['DETAIL_TEXT'],
from: 'ru',
to: $targetSite['LANGUAGE']
);
Regional adaptation — replacing contact details, phone numbers, addresses with regional ones. Implemented via templates with placeholders {{PHONE}}, {{ADDRESS}}, which are substituted during distribution.
Image substitution — different channels may require different formats or sizes. The distribution service resizes on delivery or stores versions.
Handling Edit Conflicts
If manual editing is allowed on a child site — merge logic is needed when updating from the master:
- "Always overwrite" policy — simplest, but loses local edits
-
"Don't touch manually edited" policy —
LOCALLY_MODIFIEDflag on the element, master doesn't update such items - "Field-level merge" policy — some fields always sync (title, main text), others can be local (meta tags, regional data)
Flag implementation via a custom iblock element field:
// When manually saving on the child site
CIBlockElement::SetPropertyValues($elementId, IBLOCK_ID, 'Y', 'LOCALLY_MODIFIED');
// During distribution from master
$locallyModified = CIBlockElement::GetProperty(IBLOCK_ID, $elementId, [], ['CODE' => 'LOCALLY_MODIFIED'])->Fetch();
if ($locallyModified['VALUE'] === 'Y' && $rule['respect_local_edits']) {
// Skip update
continue;
}
Queue and Asynchronous Processing
With a large number of sites or elements, synchronous distribution inside an event handler is a bad idea: the user will wait several seconds for the save to complete.
The correct approach: the event handler only creates tasks in the queue, actual distribution is performed by a worker asynchronously.
// Event handler (fast)
public static function onAfterElementUpdate(array $fields): void
{
if ($fields['IBLOCK_ID'] === MASTER_IBLOCK_ID) {
DistributionQueue::add($fields['ID'], $fields['IBLOCK_ID']);
}
}
// Worker (Cron every minute)
// php /var/www/bitrix/local/console/distribute_content.php
$pending = DistributionQueue::getPending(limit: 50);
foreach ($pending as $task) {
$distributor->distribute($task);
}
Distribution Monitoring
| Metric | How to track |
|---|---|
| Distribution lag | Difference between CREATED_AT and PUBLISHED_AT in the table |
| Failed distributions | STATUS='failed' in the last hour |
| Record count discrepancy | Compare count on master vs children |
| Queue | Size of STATUS='pending' older than N minutes |
Development Stages
| Stage | Contents | Timeline |
|---|---|---|
| Design | Distribution scheme, rules, policies | 3–5 days |
| System core | Queue, worker, status table | 1 week |
| Child site connectors | API client or direct DB access | 1 week |
| Transformations | Link adaptation, translation, regionalization | 1–2 weeks |
| Admin interface | Monitoring, manual trigger, logs | 1 week |
| Testing | Integration tests, conflict tests | 1 week |
Total: 6–10 weeks depending on the number of sites and transformation complexity.







