Developing a filter by 1C-Bitrix dimensions

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

Size Filter Development for 1C-Bitrix

A size filter in a clothing, footwear, or furniture online store is critically important for UX and directly impacts conversion. Technically it is more complex than a color filter: sizes can be numeric (36, 37, 38), alphabetic (S, M, L, XL), or combined (42/86/176), and size availability is determined at the trade offer (SKU) level, not at the parent product level.

Data Model for Sizes

In most Bitrix stores, size is an attribute of a trade offer (the SKU infoblock), not a property of the main product. Structure:

Product (CATALOG_IBLOCK_ID)
  └── Trade Offers (OFFERS_IBLOCK_ID)
        ├── SIZE = 36, COLOR = Black, QUANTITY = 5
        ├── SIZE = 37, COLOR = Black, QUANTITY = 0
        ├── SIZE = 36, COLOR = White, QUANTITY = 3
        └── SIZE = 37, COLOR = White, QUANTITY = 12

The filter "show products that have size 37" is a search over offers with PROPERTY_SIZE = 37 AND QUANTITY > 0.

Fetching Available Sizes

function getAvailableSizes(int $catalogIblockId, int $offersIblockId, int $sectionId): array
{
    // Fetch all SKUs in this section with size and stock quantity
    $rs = \CIBlockElement::GetList(
        ['PROPERTY_SIZE_VALUE' => 'ASC'],
        [
            'IBLOCK_ID'    => $offersIblockId,
            'ACTIVE'       => 'Y',
            // Filter by parent products from the required section
            'PROPERTY_CML2_LINK.SECTION_ID' => $sectionId,
        ],
        ['PROPERTY_SIZE'],
        false,
        ['PROPERTY_SIZE_VALUE', 'PROPERTY_SIZE_ENUM_ID', 'CATALOG_QUANTITY']
    );

    $sizes = [];
    while ($offer = $rs->Fetch()) {
        $sizeEnumId = $offer['PROPERTY_SIZE_ENUM_ID'];
        $sizeValue  = $offer['PROPERTY_SIZE_VALUE'];
        $qty        = (int)$offer['CATALOG_QUANTITY'];

        if (!$sizeEnumId || !$sizeValue) continue;

        if (!isset($sizes[$sizeEnumId])) {
            $sizes[$sizeEnumId] = [
                'id'        => $sizeEnumId,
                'value'     => $sizeValue,
                'available' => false,
                'count'     => 0,
            ];
        }

        if ($qty > 0) $sizes[$sizeEnumId]['available'] = true;
        $sizes[$sizeEnumId]['count']++;
    }

    return array_values($sizes);
}

Sorting Sizes

Standard alphabetic sorting produces incorrect order: 10, 6, 7, 8, 9 or L, M, S, XL, XS. Custom sorting is required:

// Sorting numeric sizes (footwear, clothing)
function sortSizes(sizes) {
  const ORDER_LETTER = ['XXS', 'XS', 'S', 'S/M', 'M', 'M/L', 'L', 'XL', 'XXL', 'XXXL', '3XL', '4XL']

  return [...sizes].sort((a, b) => {
    const aVal = a.value
    const bVal = b.value

    // Both numeric — numeric sort
    const aNum = parseFloat(aVal)
    const bNum = parseFloat(bVal)
    if (!isNaN(aNum) && !isNaN(bNum)) return aNum - bNum

    // Both alphabetic — sort by predefined order
    const aIdx = ORDER_LETTER.indexOf(aVal.toUpperCase())
    const bIdx = ORDER_LETTER.indexOf(bVal.toUpperCase())
    if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx

    // Otherwise — alphabetical
    return aVal.localeCompare(bVal, 'en')
  })
}

Vue Component for the Size Filter

<!-- SizeFilter.vue -->
<template>
  <div class="size-filter">
    <div class="size-filter__label">Size</div>

    <!-- Size system switcher (footwear) -->
    <div class="size-filter__system" v-if="hasSizeSystem">
      <button
        v-for="sys in sizeSystems"
        :key="sys.id"
        :class="{ active: activeSizeSystem === sys.id }"
        @click="activeSizeSystem = sys.id"
      >{{ sys.label }}</button>
    </div>

    <div class="size-filter__grid">
      <button
        v-for="size in sortedSizes"
        :key="size.id"
        class="size-btn"
        :class="{
          'size-btn--selected': isSelected(size.id),
          'size-btn--unavailable': !size.available,
        }"
        :disabled="!size.available && !isSelected(size.id)"
        @click="toggleSize(size.id)"
        :title="!size.available ? 'Out of stock' : `${size.count} products`"
      >
        {{ size.value }}
      </button>
    </div>

    <div class="size-filter__guide" v-if="showSizeGuide">
      <a href="/size-guide/" target="_blank">Size chart →</a>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const props = defineProps(['sizes', 'initialSelected', 'showSizeGuide'])
const emit = defineEmits(['change'])

const selected = ref(props.initialSelected || [])
const activeSizeSystem = ref('eu')

const sortedSizes = computed(() => sortSizes(props.sizes))

function isSelected(id) { return selected.value.includes(id) }

function toggleSize(id) {
  if (isSelected(id)) {
    selected.value = selected.value.filter(s => s !== id)
  } else {
    selected.value = [...selected.value, id]
  }
  emit('change', selected.value)
}
</script>

Filtering with Availability Consideration

Whether to show unavailable sizes (struck through) or hide them depends on UX requirements. Showing unavailable sizes serves as a range guide; hiding them is simpler for the buyer:

// Server-side filter split: in stock / total
public function getSizesAction(int $sectionId): array
{
    $allSizes = getAvailableSizes(CATALOG_IBLOCK_ID, OFFERS_IBLOCK_ID, $sectionId);

    return [
        'all'       => $allSizes,
        'available' => array_filter($allSizes, fn($s) => $s['available']),
    ];
}

Applying the Size Filter to the Catalog

public function filterBySizeAction(int $sectionId, array $sizeIds): array
{
    if (empty($sizeIds)) {
        return $this->getAllProductsAction($sectionId);
    }

    // Find offers with the required sizes
    $parentProductIds = [];
    $rs = \CIBlockElement::GetList(
        [],
        [
            'IBLOCK_ID'    => OFFERS_IBLOCK_ID,
            'ACTIVE'       => 'Y',
            'PROPERTY_SIZE' => $sizeIds,
            '>CATALOG_QUANTITY' => 0,
        ],
        false, false,
        ['PROPERTY_CML2_LINK']
    );
    while ($offer = $rs->Fetch()) {
        $parentId = $offer['PROPERTY_CML2_LINK_VALUE'];
        if ($parentId) $parentProductIds[] = (int)$parentId;
    }

    $parentProductIds = array_unique($parentProductIds);
    if (empty($parentProductIds)) return ['items' => [], 'total' => 0];

    // Fetch parent products
    return $this->getProductsByIds($parentProductIds);
}

Size Filter in the Bitrix Smart Filter

The standard smart filter supports filtering by offer properties via the OFFERS_PROPERTY_CODE parameter. This works, but a custom template is still needed to correctly render the size grid.

Delivery Timeline

Size grid with AJAX filtering by SKU and stock availability consideration — 2–3 business days. Full implementation with multiple size systems, a size chart, and synchronization with the color filter — 4–5 business days.