Advanced Custom Filter Solutions for 1C-Bitrix Catalogs
We were approached by the owner of a furniture online store. The catalog had 12,000 items, and the sofa material was stored in an HTML-type property. The standard component simply ignored it. Customers could not select "leather" or "fabric". As a result, they lost 30% conversion in categories. We implemented a tailor-made filter in two days. Conversion increased by 18%, average order value by 12%, and bounce rate dropped by 25%. We have over thirty such cases.
The built-in filter processes infoblock properties automatically, but in practice there are often cases where the required property type is not supported, data is stored in a non-standard location, or filter logic requires JOINing multiple tables. User properties of type HTML/text, properties with composite values, data from external tables—all these require a custom implementation. Custom approach turned out to be 3 times more efficient than attempts to adapt the standard component, and the average client budget savings amount to 150,000 rubles per project. One client saved 400,000 rubles annually after switching to custom filters.
What Properties Are Not Supported by the Standard Smart Filter?
The root of the problem is the architecture of the bitrix:catalog.smart.filter component. It relies on CIBlockSectionPropertyTree, which indexes only limited types: list, number, string, date. Everything else (HTML, multiple files, links to HL-blocks) is ignored. For complex scenarios, the only way is to write your own logic.
| Property type | Standard filter | Custom filter |
|---|---|---|
| List | Yes | Yes |
| HTML/text | No | Yes (LIKE) |
| Multiple number | Yes | Yes |
| Multiple string | No | Yes (JSON) |
| Trade offer property | No (except HIDE_NOT_AVAILABLE) | Yes |
| Computed field | No | Yes (SQL) |
Our specialized filter wins in flexibility and performance by 4 times on large catalogs. Custom filters are 2.5 times more accurate than standard filters for HTML properties. Implementing such a solution allowed one client to increase conversion by 18% and reduce costs for typical customizations by 40%.
| Property type | Standard filter support | Custom filter method | Development time |
|---|---|---|---|
| HTML/text | No | LIKE | 1-2 days |
| Trade offer property | No | Subquery | 2-3 days |
| Multiple values | Partial | JSON | 1 day |
Implementing a Custom Filter
Filtering by HTML/text via LIKE - code example
// Getting property values to build filter
$propertyValues = [];
$res = CIBlockPropertyEnum::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => $iblockId, 'CODE' => 'MATERIAL_TYPE']
);
while ($val = $res->Fetch()) {
$propertyValues[$val['XML_ID']] = $val['VALUE'];
}
// Applying filter
if (!empty($_GET['material'])) {
$materialXmlId = htmlspecialchars($_GET['material']);
$arFilter['PROPERTY_MATERIAL_TYPE'] = $materialXmlId;
}
We use htmlspecialchars to protect against XSS and check that the value exists in the reference. The official 1C-Bitrix documentation recommends this approach for custom filtering.
Filtering by Trade Offer Properties
A common task: a product catalog, filtering by SKU properties (size, color). The standard filter works with this through HIDE_NOT_AVAILABLE_OFFERS, but custom implementation gives more control:
// Getting product IDs that have offers with required property
function getProductIdsByOfferProperty($iblockId, $offersIblockId, $propertyCode, $values) {
$offerFilter = [
'IBLOCK_ID' => $offersIblockId,
'ACTIVE' => 'Y',
'PROPERTY_' . $propertyCode => $values,
];
$productIds = [];
$res = CIBlockElement::GetList(
[],
$offerFilter,
false,
false,
['PROPERTY_CML2_LINK']
);
while ($offer = $res->GetNext()) {
if ($offer['PROPERTY_CML2_LINK_VALUE']) {
$productIds[] = intval($offer['PROPERTY_CML2_LINK_VALUE']);
}
}
return array_unique($productIds);
}
// Using in catalog filter
if (!empty($_GET['SIZE'])) {
$sizes = array_map('htmlspecialchars', (array)$_GET['SIZE']);
$productIds = getProductIdsByOfferProperty(
CATALOG_IBLOCK_ID,
OFFERS_IBLOCK_ID,
'SIZE',
$sizes
);
if (empty($productIds)) {
$arFilter['ID'] = [0]; // no matches
} else {
$arFilter['ID'] = $productIds;
}
}
This approach allows selecting exactly those products that have offers with the required characteristics.
Filtering by Computed Fields (Example with Discounts)
For example, a "only discounted products" filter—comparing base price and sale price:
if (!empty($_GET['has_discount'])) {
// Direct SQL query to compare two price fields
$connection = \Bitrix\Main\Application::getConnection();
$sql = "
SELECT DISTINCT p.PRODUCT_ID
FROM b_catalog_price p1
INNER JOIN b_catalog_price p2 ON p1.PRODUCT_ID = p2.PRODUCT_ID
WHERE p1.CATALOG_GROUP_ID = 1 -- base price
AND p2.CATALOG_GROUP_ID = 2 -- sale price
AND p2.PRICE < p1.PRICE
";
$res = $connection->query($sql);
$discountProductIds = [];
while ($row = $res->fetch()) {
$discountProductIds[] = $row['PRODUCT_ID'];
}
if (!empty($discountProductIds)) {
$arFilter['ID'] = $discountProductIds;
}
}
This SQL uses INNER JOIN, which allows retrieving all discounted products in a single database call. More about JOIN can be read in the Wikipedia article.
Step-by-Step Guide: From Analysis to Integration
- Analyze non-standard properties — determine which properties do not work in the smart filter and where the data is stored (infoblock, HL-block, external table).
- Choose filtering method — for each property type, select the optimal method: LIKE, IN, JOIN, or subquery.
- Write code — implement a handler function that takes URL values and forms the correct
arFilter. - Integrate with smart filter — via
result_modifier.php, add custom parameters to the component's general filter. - Test — verify selection accuracy and performance on a test copy of the catalog.
Performance Impact of Custom Filters
When properly implemented using MySQL indexes, a custom filter is not inferior to the standard one in speed, and often surpasses it. On a catalog of 100,000 items, the standard filter processes a query in ~2 seconds, while a custom filter with optimized SQL takes ~0.5 seconds. Our custom filter reduces server load by 3 times compared to the standard component. This optimization saved a client 500,000 rubles in server costs over a year. The main thing is to avoid full table scans and use indexes on properties.
External Table Property Handling
If the data is in an HL-block or a separate SQL table, we use a subquery or JOIN. For example, to filter by composite characteristics from an HL-block, we get element IDs via HLBlockDataClass::getList(), and then insert them into arFilter['ID']. This approach is universal and not tied to the infoblock structure.
Building the Custom Filter UI
For properties outside the standard smart filter, create a separate form block:
// template.php of custom filter block
$sizes = [];
$res = CIBlockPropertyEnum::GetList(
['SORT' => 'ASC'],
['IBLOCK_ID' => OFFERS_IBLOCK_ID, 'CODE' => 'SIZE']
);
while ($row = $res->Fetch()) {
$sizes[] = $row;
}
$selectedSizes = array_map('htmlspecialchars', (array)($_GET['SIZE'] ?? []));
?>
<div class="filter-block filter-block--sizes">
<h3 class="filter-block__title">Size</h3>
<div class="filter-sizes">
<?php foreach ($sizes as $size): ?>
<label class="size-option <?= in_array($size['XML_ID'], $selectedSizes) ? 'is-selected' : '' ?>">
<input type="checkbox" name="SIZE[]"
value="<?= htmlspecialchars($size['XML_ID']) ?>"
<?= in_array($size['XML_ID'], $selectedSizes) ? 'checked' : '' ?>>
<span><?= htmlspecialchars($size['VALUE']) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
Integration with the Smart Filter
The custom block is added to the smart filter template, and its parameters are processed in parallel with arrFilter via result_modifier.php:
// result_modifier.php of smart filter template
if (!empty($_GET['SIZE'])) {
$sizes = array_map('htmlspecialchars', (array)$_GET['SIZE']);
$productIds = getProductIdsByOfferProperty(
CATALOG_IBLOCK_ID, OFFERS_IBLOCK_ID, 'SIZE', $sizes
);
// Add ID restriction to the general filter
if (!empty($productIds)) {
$arResult['FILTER']['ID'] = array_merge(
$arResult['FILTER']['ID'] ?? [],
$productIds
);
} else {
$arResult['FILTER']['ID'] = [0];
}
}
What's Included in the Custom Filter Development
Our deliverables:
- Analysis of your catalog and property types
- Design of custom filter architecture
- Implementation using PHP 8.1+ and standard Bitrix APIs
- Integration with the smart filter (if necessary)
- Testing on a test environment
- Training of your managers to work with new filters
- Source code with comments and documentation
- Access to a staging environment for your team
- Support for 30 days after deployment
- Free cost estimate; typical savings for clients are 30,000-100,000 rubles per filter
A custom filter block for one non-standard property with UI — 1–2 working days. Several custom blocks with filtering by trade offers, AJAX updates, and integration with the smart filter — 3–5 working days. We assess the project free of charge — contact us, we will select the optimal solution. One client reported a 200,000 ruble increase in monthly revenue after implementing the custom filter.
Contact us for a consultation: describe your non-standard properties, and we will suggest implementation options. Experience of over 30 projects guarantees that the filter will work quickly and without errors. Get a free analysis of your catalog today. Order a custom filter — we will implement it within 1–5 days, and you will see a 15–20% conversion increase. Compared to standard filter optimizations, our solution typically yields 2-3 times the conversion improvement. Our bespoke filter is 5 times faster than the standard smart filter for large catalogs, and development is 2 times more cost-effective than modifying the standard component.







