Developing filtering by user properties in 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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_cache table with pre-computed values for filtering
  • Elasticsearch/Sphinx: index properties in a search engine and perform faceted search there
  • Result caching: \Bitrix\Main\Data\Cache for 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.