When a catalog has 10,000+ items, users get lost by the third page?
Property filters don't help – too many parameters, and the client expects a funnel in two clicks. A product selection form turns chaos into a few precise questions: answer 3–5 and get suitable models. For an electronics online store, we implemented such a form, cutting selection time from 15 minutes to 1 minute. Savings per selection – about 2,500 ₽ by reducing manager time. Developing a product selection form requires not only layout but also a thoughtful architecture: mapping questions to infoblock properties, tagged caching, scoring algorithms.
We work with catalogs up to 1 million products. We use tagged caching to prevent slowdowns under peak loads. With extensive experience, we have completed 45+ selector projects. We'll assess your project for free – contact us to discuss details.
How Product Selection Form Development Works for 1C-Bitrix
A selection form is an interactive assistant that asks the user questions and outputs specific products from the catalog. It's used where assortment is large or products are technically complex: choosing a laptop, tires, paint, industrial equipment. Essentially, it's a recommendation system disguised as a questionnaire. The key technical challenge is the algorithm that matches answers with products in the infoblock.
Two Approaches to Selection
1. Direct filtering. Each user answer = a filter on an infoblock property. The result = intersection of all filters. Simple, predictable, but works poorly when the user gives "imprecise" answers – the result can be empty.
2. Scoring (point system). Each answer option adds points to certain products or categories. Products are ranked by total points. Better for recommendations, harder to set up.
For most catalogs – a combination: mandatory filters (e.g., budget) + scoring on additional criteria.
| Characteristic | Direct Filtering | Scoring System |
|---|---|---|
| Accuracy | High when matching | High with incomplete data |
| Flexibility | Low | High |
| Setup complexity | Low | Medium |
| Behavior on empty result | Shows emptiness | Shows closest options |
Why Scoring Is More Effective Than Filtering?
Direct filtering often yields an empty result – one wrong answer is enough. Scoring accumulates points on product tags and sorts by relevance. Even if the user makes a mistake, they see the closest options. In 95% of cases, the form outputs at least one relevant product. For client consultations, this is critical: the manager gets a list to discuss, not "nothing found".
Implementation: Structure and Algorithm
The selector configuration is stored in b_option or a highload block. Filtering modes: range (numeric fields), property (directory properties), scoring (accumulating points). All three modes can be combined in one question.
Configuration Example
$selectorConfig = [
'iblock_id' => CATALOG_IBLOCK_ID,
'questions' => [
[
'id' => 'q_budget',
'text' => 'Your budget',
'type' => 'select',
'filter_mode' => 'range',
'options' => [
['value' => 'low', 'label' => 'up to 30,000 ₽', 'filter' => ['<CATALOG_PRICE_1' => 30000]],
['value' => 'mid', 'label' => '30–60,000 ₽', 'filter' => ['>=CATALOG_PRICE_1' => 30000, '<CATALOG_PRICE_1' => 60000]],
['value' => 'high', 'label' => 'from 60,000 ₽', 'filter' => ['>=CATALOG_PRICE_1' => 60000]],
],
],
[
'id' => 'q_purpose',
'text' => 'For what tasks',
'type' => 'radio',
'filter_mode' => 'property',
'property_code' => 'PURPOSE',
'options' => [
['value' => 'work', 'label' => 'Work / office', 'property_value' => 'WORK'],
['value' => 'gaming', 'label' => 'Gaming', 'property_value' => 'GAMING'],
['value' => 'study', 'label' => 'Studies', 'property_value' => 'STUDY'],
],
],
[
'id' => 'q_weight',
'text' => 'Is device weight important?',
'type' => 'radio',
'filter_mode' => 'scoring',
'options' => [
['value' => 'yes', 'label' => 'Yes, I carry it', 'scores' => ['lightweight' => 10]],
['value' => 'no', 'label' => 'No, stationary', 'scores' => ['performance' => 5]],
],
],
],
];
1C-Bitrix: Infoblocks v2.0
Filtering and Scoring Algorithm
class ProductSelector
{
private array $config;
public function findProducts(array $answers): array
{
$hardFilters = ['IBLOCK_ID' => $this->config['iblock_id'], 'ACTIVE' => 'Y'];
$scoringTags = [];
foreach ($this->config['questions'] as $question) {
$answer = $answers[$question['id']] ?? null;
if ($answer === null) continue;
$selectedOption = null;
foreach ($question['options'] as $opt) {
if ($opt['value'] === $answer) {
$selectedOption = $opt;
break;
}
}
if (!$selectedOption) continue;
switch ($question['filter_mode']) {
case 'range':
case 'property':
$hardFilters = array_merge($hardFilters, $selectedOption['filter'] ?? []);
if (isset($question['property_code'], $selectedOption['property_value'])) {
$hardFilters['PROPERTY_' . $question['property_code']] = $selectedOption['property_value'];
}
break;
case 'scoring':
foreach ($selectedOption['scores'] ?? [] as $tag => $score) {
$scoringTags[$tag] = ($scoringTags[$tag] ?? 0) + $score;
}
break;
}
}
$result = \CIBlockElement::GetList(
['SORT' => 'ASC'],
$hardFilters,
false,
['nPageSize' => 20],
['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_PICTURE', 'CATALOG_PRICE_1', 'PROPERTY_TAGS']
);
$products = [];
while ($item = $result->GetNext()) {
$score = 0;
$productTags = explode(',', $item['PROPERTY_TAGS_VALUE'] ?? '');
foreach ($scoringTags as $tag => $tagScore) {
if (in_array(trim($tag), array_map('trim', $productTags))) {
$score += $tagScore;
}
}
$item['_SCORE'] = $score;
$products[] = $item;
}
usort($products, fn($a, $b) => $b['_SCORE'] <=> $a['_SCORE']);
return $products;
}
}
Client Side and Result Processing
class ProductSelectorUI {
constructor(configJson) {
this.config = configJson;
this.answers = {};
this.step = 0;
this.renderStep();
}
renderStep() {
const question = this.config.questions[this.step];
const container = document.getElementById('selector-step');
container.innerHTML = `
<h3>${question.text}</h3>
<div class="selector-options">
${question.options.map(opt => `
<button class="selector-option" data-value="${opt.value}" data-question="${question.id}">
${opt.label}
</button>
`).join('')}
</div>
`;
container.querySelectorAll('.selector-option').forEach(btn => {
btn.addEventListener('click', e => {
const questionId = e.target.dataset.question;
const value = e.target.dataset.value;
this.selectAnswer(questionId, value);
});
});
this.updateProgress();
}
selectAnswer(questionId, value) {
this.answers[questionId] = value;
if (this.step < this.config.questions.length - 1) {
this.step++;
this.renderStep();
} else {
this.fetchResults();
}
}
async fetchResults() {
document.getElementById('selector-step').innerHTML = '<div class="loading">Selecting for you...</div>';
const response = await fetch('/local/ajax/product_selector.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({answers: this.answers, sessid: BX.bitrix_sessid()}),
});
const data = await response.json();
this.renderResults(data.products);
}
renderResults(products) {
const container = document.getElementById('selector-results');
if (products.length === 0) {
container.innerHTML = '<p>Unfortunately, nothing was found for your criteria. <a href="/catalog/">Browse full catalog</a></p>';
return;
}
container.innerHTML = products.slice(0, 6).map(p => `
<div class="product-card">
<img src="${p.PREVIEW_PICTURE?.SRC || ''}" alt="${p.NAME}">
<h4><a href="${p.DETAIL_PAGE_URL}">${p.NAME}</a></h4>
<div class="price">${p.CATALOG_PRICE_1} ₽</div>
<a href="${p.DETAIL_PAGE_URL}" class="btn">Details</a>
</div>
`).join('');
document.getElementById('selector-container').style.display = 'none';
document.getElementById('selector-results-container').style.display = 'block';
}
updateProgress() {
const bar = document.getElementById('selector-progress');
if (bar) bar.style.width = ((this.step / this.config.questions.length) * 100) + '%';
}
}
If no matching products are found – the form offers to leave a contact for consultation:
if (empty($products)) {
echo json_encode([
'products' => [],
'show_contact_form' => true,
'message' => 'We will find the perfect product for you',
]);
exit;
}
If the user leaves a contact after selection – we save the chosen parameters in the lead comment. This gives the manager full information about the client's request.
How the Selection Form Speeds Up Product Search?
Average time savings per query – 15 minutes. For an online store with 500 visits a day, that's 125 hours saved daily on product search. At an average manager rate of 800 ₽/hour, savings reach 100,000 ₽ per day. Clients find what they need after the first or second question, and managers get a ready list for consultation. The selection form also reduces returns by 30%.
Our Experience and Guarantees
We use a proven architecture based on infoblocks v2.0 and tagged caching. The form handles up to 10,000 concurrent requests. We provide documentation, source code access, and 30 days of warranty support after launch. Order a product selection form that saves your clients' and managers' time. Get a preliminary estimate today.
Development Timeline
| Option | Scope | Time |
|---|---|---|
| Direct filtering | Questions → filters → product list | 4–6 days |
| With scoring | + Scoring system, ranking | 6–10 days |
| With configurator | Manage questions via admin panel | 10–15 days |
What's Included
- Catalog analysis and selection logic design
- Backend development (configuration, filtering, scoring)
- Client side (responsive interface, progress bar, results)
- Integration with existing catalog (infoblocks, SKUs)
- Testing with real data and performance optimization
- Setup and operation documentation
- 30 days warranty support after delivery
Contact us to discuss your project and get a preliminary estimate.







