Custom Property Filtering for 1C-Bitrix
Custom properties (UF — User Fields, or infoblock properties) are custom product attributes: material, country of manufacture, warranty period, compatible models, technical specifications. The standard Bitrix smart filter can work with infoblock properties, but non-standard property types, multiple values, and dependent filters require custom development.
Property Types and Their Filtering
| Property Type | Storage | Filtering in GetList |
|---|---|---|
| String (S) | b_iblock_element_prop_s* |
PROPERTY_CODE = "value" |
| Number (N) | b_iblock_element_prop_s* |
>=PROPERTY_CODE, <=PROPERTY_CODE |
| List (L) | b_iblock_element_prop_s* |
PROPERTY_CODE = $enumId |
| List (multiple) | b_iblock_element_prop_m* |
PROPERTY_CODE = [$id1, $id2] |
| Element link | b_iblock_element_prop_s* |
PROPERTY_CODE = $elementId |
Filtering by List Property
A property of type "list" stores the value ID from b_iblock_property_enum, not the text value:
// Retrieve value IDs for the filter
$enumValues = [];
$rsEnum = \CIBlockPropertyEnum::GetList(
['SORT' => 'ASC'],
['PROPERTY_ID' => $propertyId, 'ACTIVE' => 'Y']
);
while ($enum = $rsEnum->Fetch()) {
$enumValues[$enum['XML_ID']] = $enum['ID'];
}
// Filter by selected values (OR logic within the property)
$selectedValues = ['cotton', 'linen']; // XML_ID values from the request
$filterIds = array_filter(
array_map(fn($xmlId) => $enumValues[$xmlId] ?? null, $selectedValues)
);
$filter['PROPERTY_MATERIAL'] = array_values($filterIds);
Multiple-Value Properties
A product can have multiple values for a single property (e.g., "Compatible Models"). Filtering works as OR:
// Products compatible with at least one of the selected models
$filter['PROPERTY_COMPATIBLE_MODELS'] = [123, 456, 789]; // array of element IDs
// For AND logic (product is compatible with all selected models) — multiple conditions:
foreach ($requiredModels as $modelId) {
$filter["PROPERTY_COMPATIBLE_MODELS_{$modelId}"] = $modelId;
}
// This is a non-standard approach — in complex cases, a raw SQL query is preferable
Dependent Filters
Selecting "Device Type: Laptop" hides irrelevant values in "Processor". Implemented via an AJAX request when the parent filter changes:
// Method for retrieving available values based on current filters
public function getFilterValuesAction(
int $sectionId,
string $propertyCode,
array $currentFilter = []
): array {
// Apply the current filter, but do not filter by the requested property
$filter = $this->buildBaseFilter($sectionId, $currentFilter);
unset($filter["PROPERTY_{$propertyCode}"]);
$values = [];
$rs = \CIBlockElement::GetList(
[],
$filter,
["PROPERTY_{$propertyCode}"],
false,
["PROPERTY_{$propertyCode}_VALUE", "PROPERTY_{$propertyCode}_ENUM_ID", 'ID']
);
while ($el = $rs->Fetch()) {
$enumId = $el["PROPERTY_{$propertyCode}_ENUM_ID"];
$value = $el["PROPERTY_{$propertyCode}_VALUE"];
if ($enumId && $value) {
$values[$enumId] = [
'id' => $enumId,
'label' => $value,
'count' => ($values[$enumId]['count'] ?? 0) + 1,
];
}
}
return array_values($values);
}
On the frontend — a filter change handler that re-requests available values:
document.querySelectorAll('[data-filter-prop]').forEach(select => {
select.addEventListener('change', async () => {
const currentFilter = getActiveFilters()
const propertyCode = select.dataset.filterProp
// Request up-to-date values for dependent filters
const dependentProps = getDependentProperties(propertyCode)
for (const depProp of dependentProps) {
const values = await fetchFilterValues(depProp, currentFilter)
updateFilterOptions(depProp, values)
}
})
})
Tree-Type Properties and Hierarchical Lists
The "Region of Manufacture" property can be hierarchical: Asia → China → Shenzhen. Filtering by a parent node must include all descendants:
function getDescendantEnumIds(int $parentEnumId, int $propertyId): array
{
$ids = [$parentEnumId];
$rs = \CIBlockPropertyEnum::GetList(
[],
['PROPERTY_ID' => $propertyId, 'IBLOCK_PROPERTY.CODE' => 'REGION']
);
while ($enum = $rs->Fetch()) {
$defVal = $enum['DEF_VALUE'] ?? '';
if ($defVal === (string)$parentEnumId) {
$ids[] = (int)$enum['ID'];
$ids = array_merge($ids, getDescendantEnumIds($enum['ID'], $propertyId));
}
}
return array_unique($ids);
}
$filter['PROPERTY_REGION'] = getDescendantEnumIds($selectedRegionId, $propertyId);
Numeric Ranges for UF Properties
Number-type properties — warranty period, weight, power — are filtered by ranges:
// Range for a numeric property
if (!empty($filterData['warranty_min'])) {
$filter['>=PROPERTY_WARRANTY_MONTHS'] = (int)$filterData['warranty_min'];
}
if (!empty($filterData['warranty_max'])) {
$filter['<=PROPERTY_WARRANTY_MONTHS'] = (int)$filterData['warranty_max'];
}
Performance with Many Properties
Filtering by 5+ properties simultaneously via CIBlockElement::GetList involves a series of JOINs to property tables. For catalogs > 20,000 products, such queries are slow. Solutions:
- Denormalization: create a dedicated
product_filter_cachetable with pre-computed values for filtering - Elasticsearch/Sphinx: index properties in a search engine and perform faceted search there
- Result caching:
\Bitrix\Main\Data\Cachefor 10–15 minutes with an invalidation tag on product changes
Timeline
Filter by 3–5 list properties with AJAX updates — 3–5 business days. Dependent filters with hierarchical values and numeric ranges — 5–8 business days.







