Development of Custom Filters in Bitrix24
The built-in Bitrix24 filter works on the principle of "field — operator — value". For simple selections this is sufficient. But when you need to filter deals by aggregated data (sum of payments over period > threshold), by related entities (contacts that have deals at a certain stage) or by data from external systems (clients with debt in 1C) — the standard filter is useless. It doesn't support joins, doesn't support subqueries, and knows nothing about external sources.
How the Standard Filter Works
In Bitrix24 filtering is handled by JS component BX.Main.Filter. It renders the filter panel, collects user input and passes it to backend handler. In the on-premise version the filter operates with ORM classes: \Bitrix\Crm\DealTable, \Bitrix\Crm\ContactTable, etc. Each filter field maps to an ORM entity column.
In the cloud version filtering happens via REST API — parameter filter in methods crm.deal.list, crm.item.list. Supported operators:
-
=— exact match -
!— not equal -
<,>,<=,>=— comparison -
%— contains (LIKE) - Array of values — IN
Missing: grouping conditions (AND/OR at filter level), aggregate functions, subqueries.
Custom Filter: Architecture
A custom filter is a REST application that implements its own selection logic and returns results to B24 interface. Architecture consists of three layers:
1. Filter UI — your interface in iframe (placement LEFT_MENU or CRM_*_LIST_TOOLBAR). User sets filtering parameters: period, sum threshold, type of related entity.
2. Backend handler — your server that receives filter parameters, performs complex selection and returns list of IDs of matching items.
3. Results display — filtered data shown in your custom list or passed back to standard list via preset filter.
Deep Dive: Filtering by Aggregates
Task: show manager deals where sum of payments for last 30 days is less than 50% of deal sum. Standard filter can't do this.
Step 1: Data collection. Backend handler requests all active deals via crm.deal.list with filter STAGE_SEMANTIC_ID=P. For each deal gets product items and payment history via crm.timeline.list or custom field with payment sum.
Step 2: Aggregation on backend. This is the key — calculations happen on your server, not in browser. For each deal:
payment_percent = sum_payments_30days / deal_sum * 100
if payment_percent < 50 → include in result
Step 3: Caching. Aggregation result is cached (Redis, memcached) with TTL 15–30 minutes. Re-opening the filter doesn't trigger recalculation.
Step 4: Display. List of IDs of filtered deals is passed to UI, which renders table with needed columns. Or — more elegantly — a preset filter ID is formed in standard list:
// Open standard list with preset filter
BX24.openPath('/crm/deal/list/?apply_filter=Y&ID[]=' + filteredIds.join('&ID[]='));
Filtering by Related Entities
Scenario: find all companies that have at least one deal at "Negotiations" stage with sum > 1,000,000.
In the standard company filter there are no deal fields. Solution:
- Request
crm.deal.listwith filter by stage and sum → getCOMPANY_IDfrom each deal - Unique
COMPANY_ID→ array of IDs for company filter - Request
crm.company.listwithfilter: {ID: uniqueCompanyIds}→ final list
On your backend this is one SQL query with JOIN. Via REST API — chain of 2–3 requests. For regular use of such filter, create server endpoint that performs aggregation and returns ready result.
Custom Filter Presets
Besides dynamic filters, saved presets are often needed: "Overdue > 7 days", "VIP-clients with no activity", "Deals without tasks". REST API has no method for programmatically creating presets of B24's standard filter. Solution — custom UI with preset buttons:
var presets = {
overdue_7: {'>DATE_CLOSE': formatDate(-7), 'STAGE_SEMANTIC_ID': 'P'},
vip_inactive: {'UF_CRM_VIP': 1, '<DATE_MODIFY': formatDate(-30)},
no_tasks: {} // Requires server logic
};
function applyPreset(name) {
loadFilteredData(presets[name]);
}
The "Deals without tasks" preset is an example of a filter impossible via REST: you need to check the absence of related tasks for each deal. This is solved by batch request tasks.task.list with filter UF_CRM_TASK for each deal or server aggregation.
Performance and Limits
Bitrix24 REST API limits request quantity: 2 requests per second for one application, 50 commands in batch. For a filter that should process 10,000 deals:
| Approach | Requests | Time |
|---|---|---|
| Sequential requests by 50 | 200 | ~100 sec |
| Batch by 50 commands | 4 batch requests | ~8 sec |
| Server cache + incremental update | 1–2 requests | <1 sec |
For production filters with large volumes the only workable approach is server cache with periodic synchronization. Your server stores a copy of CRM data, updated via webhooks (onCrmDealUpdate) or on schedule, and performs filtering locally.
On-Premise Version: Filters Through ORM
In on-premise B24 custom filter can be implemented at PHP level — extending standard filter with new fields. Through event onBuildFilterFields a custom field is added, and handler onBuildFilterQuery modifies the SQL query. This is more performant than REST approach, but requires server access and updates with module.







