Development of Product Bundle Constructor on 1C-Bitrix
A customer wants to assemble a gift basket from 5 items of their choice. Or build a computer from compatible components. Or select tires + wheels + TPMS as one order. None of the standard Bitrix components — neither bitrix:catalog.element nor trading offers — solves this task. You need a bundle constructor.
How Constructor Differs from Trading Offers
Trading offers (SKU) in Bitrix are variations of one product: size, color. Bundle constructor is a combination of different products from different categories, assembled into one order with a combined price. Sometimes with a fixed number of items per "slot", sometimes arbitrary choice.
Examples:
- Gift set: choose 3 from 20 cosmetic options
- PC constructor: choose processor + motherboard + RAM (with compatibility checking)
- Combo set: burger + drink + side (one item from each group)
Data Structure
Bundle is a separate entity. Implementation option — infoblock "Bundles" with binding to catalog products.
Bundles infoblock (IBLOCK_SETS):
-
SET_NAME— bundle name -
SET_DESCRIPTION— description -
SLOTS— JSON describing bundle slots (multiple string property or separate table) -
DISCOUNT_TYPE— bundle discount type (percent/fixed) -
DISCOUNT_VALUE— discount amount
Slot structure in JSON:
{
"slot_id": 1,
"name": "Main course",
"required": true,
"min_qty": 1,
"max_qty": 1,
"product_filter": {
"IBLOCK_ID": 5,
"SECTION_ID": [12, 13]
}
}
For complex projects (PC constructor), store slots in separate custom table:
CREATE TABLE b_set_slots (
ID SERIAL PRIMARY KEY,
SET_ID INT NOT NULL REFERENCES b_iblock_element(ID),
SLOT_NAME VARCHAR(255),
REQUIRED BOOLEAN DEFAULT TRUE,
MIN_QTY INT DEFAULT 1,
MAX_QTY INT DEFAULT 1,
SORT INT DEFAULT 100
);
CREATE TABLE b_set_slot_products (
SLOT_ID INT NOT NULL REFERENCES b_set_slots(ID),
PRODUCT_ID INT NOT NULL REFERENCES b_iblock_element(ID),
PRIMARY KEY (SLOT_ID, PRODUCT_ID)
);
Selection Logic and Price Calculation
User interface — sequential steps or single page with vertical groups. Each step — product list for given slot.
Final price calculation happens client-side (fast, no server) and is verified server-side (when adding to cart):
function calcSetTotal(selectedProducts) {
let total = selectedProducts.reduce((sum, p) => sum + p.price * p.qty, 0);
// apply bundle discount
if (setDiscount.type === 'percent') {
total = total * (1 - setDiscount.value / 100);
} else {
total = total - setDiscount.value;
}
return Math.max(0, total);
}
Adding Bundle to Cart
Standard Bitrix cart API can't add "related" positions that make up a bundle. Custom logic needed:
use Bitrix\Sale\Basket;
use Bitrix\Sale\BasketItem;
$basket = Basket::loadItemsForFUser(CSaleBasket::GetBasketUserID(), SITE_ID);
$setBasketCode = 'set_' . uniqid(); // unique bundle identifier
foreach ($selectedProducts as $product) {
$item = $basket->createItem('catalog', $product['PRODUCT_ID']);
$item->setFields([
'QUANTITY' => $product['QTY'],
'PRODUCT_PRICE_ID' => $product['PRICE_ID'],
'CUSTOM_PRICE' => 'Y',
'PRICE' => $product['PRICE'],
// custom field for grouping bundle items
'PROPS' => [['NAME' => 'SET_CODE', 'VALUE' => $setBasketCode]],
]);
}
$basket->save();
In cart, bundle items display grouped by SET_CODE. Cart template (bitrix:sale.basket.basket) needs customization — add grouping by bundle property.
Compatibility Checking
For technical products (PC, auto parts, furniture to size) compatibility checking needed between selected items. Compatibility matrix stored in custom table:
CREATE TABLE b_compatibility_rules (
ID SERIAL PRIMARY KEY,
PRODUCT_A_ID INT NOT NULL,
PRODUCT_B_ID INT NOT NULL,
COMPATIBLE BOOLEAN DEFAULT TRUE,
REASON TEXT
);
Each product choice triggers AJAX request to PHP controller checking compatibility with already selected items and returns list of incompatible options for UI blocking:
public function checkCompatibilityAction(int $productId, array $selectedIds): array {
$incompatible = [];
foreach ($selectedIds as $id) {
$rule = CompatibilityTable::getRow([
'filter' => [
['PRODUCT_A_ID' => $productId, 'PRODUCT_B_ID' => $id],
['COMPATIBLE' => false],
]
]);
if ($rule) {
$incompatible[] = ['product_id' => $id, 'reason' => $rule['REASON']];
}
}
return $incompatible;
}
Saved Configurations
User may want to save assembled bundle and return to it. Save configuration in user account:
CREATE TABLE b_user_set_configs (
ID SERIAL PRIMARY KEY,
USER_ID INT NOT NULL,
SET_ID INT NOT NULL,
CONFIG_NAME VARCHAR(255),
PRODUCTS JSONB NOT NULL,
DATE_CREATE TIMESTAMP DEFAULT NOW()
);
User sees their configurations in personal account and can add any to cart with one click.
Timeframes
| Constructor Type | What's Included | Timeframe |
|---|---|---|
| Simple gift set | UI + cart + bundle discount | 2–3 weeks |
| Combo with groups | + group slots, required items | 3–5 weeks |
| Constructor with compatibility | + compatibility matrix, checks | 5–8 weeks |
| + Saved configs | + personal account, history, sharing | +2–3 weeks |
Bundle constructor increases average order value: user assembling a kit spends more than one buying a single product. Tool works better the simpler and clearer the selection interface.







