Developing brand filtering for 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

Brand Filtering for 1C-Bitrix

The brand filter is one of the key features in most online stores. Technically this is filtering by a list property or by a link to an element in a dedicated brands infoblock. However, a brand filter demands special UX: logos next to the brand name, alphabetical grouping for large brand sets, highlighting popular brands, and fast brand search.

Two Ways to Store Brands

Method 1: List property (PROPERTY_TYPE = L). Values in b_iblock_property_enum. Simple, but limited: no way to store a logo image or additional attributes.

Method 2: Element link to an infoblock (PROPERTY_TYPE = E). A dedicated "Brands" infoblock with its own properties: logo, country, description, SEO data. More flexible, used in large catalogs.

Retrieving Brands with Product Counts

// For a list property
function getBrandsWithCount(int $iblockId, int $sectionId): array
{
    $brands = [];

    // Get all values of the BRAND property with product counts
    $rs = \CIBlockElement::GetList(
        ['PROPERTY_BRAND_VALUE' => 'ASC'],
        [
            'IBLOCK_ID'  => $iblockId,
            'ACTIVE'     => 'Y',
            'SECTION_ID' => $sectionId,
            'INCLUDE_SUBSECTIONS' => 'Y',
        ],
        ['PROPERTY_BRAND'],
        false,
        ['ID', 'PROPERTY_BRAND_VALUE', 'PROPERTY_BRAND_ENUM_ID']
    );

    while ($el = $rs->Fetch()) {
        $enumId = $el['PROPERTY_BRAND_ENUM_ID'];
        $name = $el['PROPERTY_BRAND_VALUE'];
        if ($enumId) {
            $brands[$enumId]['name'] = $name;
            $brands[$enumId]['id'] = $enumId;
            $brands[$enumId]['count'] = ($brands[$enumId]['count'] ?? 0) + 1;
        }
    }

    return array_values($brands);
}

For an element link — join with the brands infoblock to retrieve the logo:

function getBrandsWithLogos(int $iblockId, int $brandIblockId): array
{
    // First, get unique brand IDs present in the catalog
    $brandIds = [];
    $rs = \CIBlockElement::GetList(
        [],
        ['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y'],
        ['PROPERTY_BRAND'],
        false,
        ['PROPERTY_BRAND_VALUE']
    );
    while ($el = $rs->Fetch()) {
        if ($el['PROPERTY_BRAND_VALUE']) {
            $brandIds[] = (int)$el['PROPERTY_BRAND_VALUE'];
        }
    }
    $brandIds = array_unique($brandIds);

    // Then retrieve brand data including logos
    $brands = [];
    $brandRs = \CIBlockElement::GetList(
        ['SORT' => 'ASC'],
        ['IBLOCK_ID' => $brandIblockId, 'ID' => $brandIds, 'ACTIVE' => 'Y'],
        false,
        false,
        ['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL']
    );
    while ($brand = $brandRs->Fetch()) {
        $logo = $brand['PREVIEW_PICTURE']
            ? \CFile::GetPath($brand['PREVIEW_PICTURE'])
            : null;

        $brands[] = [
            'id'   => $brand['ID'],
            'name' => $brand['NAME'],
            'logo' => $logo,
            'url'  => $brand['DETAIL_PAGE_URL'],
        ];
    }

    return $brands;
}

Brand Filter Template

<!-- Brand filter with logos -->
<div class="brand-filter">
  <div class="brand-filter__search">
    <input type="text" id="brand-search" placeholder="Search brand..." />
  </div>

  <div class="brand-filter__popular">
    <span class="brand-filter__label">Popular</span>
    <div class="brand-filter__logos" id="popular-brands">
      <!-- Top-10 brand logos -->
    </div>
  </div>

  <div class="brand-filter__list" id="brand-list">
    <div class="brand-filter__group" v-for="letter in alphabet">
      <div class="brand-filter__letter">{{ letter }}</div>
      <label v-for="brand in brandsByLetter[letter]" :key="brand.id">
        <input type="checkbox" :value="brand.id" v-model="selectedBrands" />
        <img v-if="brand.logo" :src="brand.logo" :alt="brand.name" loading="lazy" />
        <span>{{ brand.name }}</span>
        <span class="brand-filter__count">({{ brand.count }})</span>
      </label>
    </div>
  </div>
</div>

Alphabetical Grouping and Search

// Group brands by first letter
function groupBrandsByLetter(brands) {
  const groups = {}
  const cyrillic = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'

  brands.forEach(brand => {
    const firstChar = brand.name[0].toUpperCase()
    const letter = cyrillic.includes(firstChar) || /[A-Z]/.test(firstChar)
      ? firstChar
      : '#'

    if (!groups[letter]) groups[letter] = []
    groups[letter].push(brand)
  })

  return groups
}

// Search with debounce
const searchInput = document.getElementById('brand-search')
let searchTimer = null

searchInput.addEventListener('input', () => {
  clearTimeout(searchTimer)
  searchTimer = setTimeout(() => {
    const query = searchInput.value.toLowerCase()
    filterBrandsList(query)
  }, 200)
})

function filterBrandsList(query) {
  document.querySelectorAll('.brand-item').forEach(item => {
    const name = item.dataset.brandName.toLowerCase()
    item.style.display = name.includes(query) ? '' : 'none'
  })

  // Hide empty letter groups
  document.querySelectorAll('.brand-filter__group').forEach(group => {
    const visibleItems = group.querySelectorAll('.brand-item:not([style*="none"])')
    group.style.display = visibleItems.length ? '' : 'none'
  })
}

Brand Page in the Catalog

Clicking a brand in the filter leads to a filtered catalog or a dedicated brand page. For SEO — a dedicated page is preferable:

// Routing: /catalog/brand/apple/ → filter by brand
$brandCode = $arUrlTemplates['brand']; // from human-readable URL
$brandElement = \CIBlockElement::GetList(
    [],
    ['IBLOCK_ID' => BRANDS_IBLOCK_ID, 'CODE' => $brandCode],
    false, false, ['ID', 'NAME']
)->Fetch();

if ($brandElement) {
    $arFilter['PROPERTY_BRAND'] = $brandElement['ID'];
}

Popular Brands

Top brands by product count or by views — displayed as logos in a dedicated block:

// Sort by product count — determined when building the brand list
usort($brands, fn($a, $b) => $b['count'] - $a['count']);
$popularBrands = array_slice($brands, 0, 10);

Timeline

Brand filter (list property) with checkboxes and AJAX — 1–2 business days. Full-featured brand filter with logos, search, alphabetical grouping, and brand pages — 3–4 business days.