Widget Development for Bitrix24
A classic situation: CRM is already in use, managers are tracking all deals, but you need to add a block with data from an external system to the deal card — for example, warehouse stock from ERP or delivery status from a logistics company. This cannot be done with built-in tools; a widget is required via the REST API and the UI Extensions embedding mechanism.
Widgets in Bitrix24 are a distinct type of application that renders inside the portal interface through an iframe or JS SDK. Since 2022, Bitrix has been developing the UI Extensions concept as a replacement for classic iframe widgets. Let's break down what to use when, and how it is done in practice.
Widget Types and Embedding Locations
Bitrix24 supports several embedding mechanisms:
-
Placement API (
BX24.placement.call) — the classic method: an application registers a placement, and Bitrix24 displays it in the appropriate location in the interface -
UI Extensions — a set of ready-made components (Button, Dialog, Loader, Alert) available via
@bitrix24/b24jssdk. They work inside an iframe but with access to the portal's event bus -
Slider — opening an arbitrary URL in a side panel via
BX24.openApplication()
Active placements are registered when the application is installed via the app.option.set method and are stored in the b_app_option table. Available embedding locations:
| Placement | Where It Appears |
|---|---|
CRM_DEAL_DETAIL_TAB |
Tab on the deal card |
CRM_LEAD_DETAIL_TAB |
Tab on the lead card |
CRM_CONTACT_DETAIL_ACTIVITY |
Activity in the contact timeline |
TASK_VIEW_TAB |
Tab in a task |
CALL_CARD |
Call card |
TELEPHONY_CALL_BEFORE_ANSWER |
Before answering a call |
TOP_MENU_ITEM |
Top menu item |
iframe Widget Architecture
A typical widget consists of two parts: a server-side handler (an endpoint that returns the HTML of the widget page) and client-side code inside the iframe.
Initialization in the client-side code:
BX24.init(() => {
const placement = BX24.placement.info();
// placement.options contains context: deal id, user id, etc.
const dealId = placement.options.ID;
BX24.callMethod('crm.deal.get', { id: dealId }, (result) => {
if (result.error()) {
console.error(result.error());
return;
}
renderWidget(result.data());
});
});
iframe height — a frequent issue. Bitrix24 does not make the iframe elastic automatically. After rendering content, you must explicitly call:
BX24.fitWindow(() => {
// callback after height change
});
If this is not done, the widget will be clipped or an internal scrollbar will appear.
UI Extensions: The Modern Approach
Starting from portal version 2024+, using @bitrix24/b24jssdk is recommended. It provides typed access to the REST API directly from within the iframe:
import { initializeB24Frame, B24Frame } from '@bitrix24/b24jssdk';
const $b24 = await initializeB24Frame();
const profile = await $b24.fetchProfile();
const result = await $b24.callMethod('crm.deal.list', {
filter: { ASSIGNED_BY_ID: profile.id },
select: ['ID', 'TITLE', 'STAGE_ID']
});
The SDK handles authorization automatically (the OAuth token is passed via postMessage), so client_secret does not need to be stored on the client.
Placement Registration and Application Manifest
The application registers placements upon installation via the OnAppInstall hook. Example in the manifest:
{
"placements": [
{
"placement": "CRM_DEAL_DETAIL_TAB",
"handler": "https://myapp.example.com/widget/deal-tab",
"title": "ERP Data",
"description": "Stock and reservations for deal line items"
}
]
}
Or programmatically via REST:
POST /rest/placement.bind
{
"PLACEMENT": "CRM_DEAL_DETAIL_TAB",
"HANDLER": "https://myapp.example.com/widget/deal-tab",
"TITLE": "Warehouse"
}
Authorization and Security
All requests from the iframe to the server must pass the AUTH_ID (a short-lived token, 1 hour) or use REFRESH_TOKEN to renew it. Never store client_secret in client-side code — only on the server.
Validate the incoming event from postMessage:
window.addEventListener('message', (event) => {
if (event.origin !== 'https://your-portal.bitrix24.ru') return;
// processing
});
Common Challenges
CSP (Content Security Policy) — Bitrix24 restricts frame-ancestors. Your application domain must be whitelisted, which is configured automatically when publishing in the Marketplace or registering a local application.
Slow BX24.init() initialization — if the widget takes longer than 3 seconds to load, users switch to another tab. Optimization: load bx24.js via CDN, show a skeleton loader while waiting for data.
Cross-domain cookies — for session-based authentication with your own backend, use SameSite=None; Secure, otherwise the browser will block cookies inside the iframe.
Development Timeline
| Widget Type | Scope | Timeline |
|---|---|---|
| Simple informational widget (CRM data read-only) | S | 1–2 days |
| Interactive widget with CRM write | M | 3–5 days |
| Widget with external system integration | L | 1–2 weeks |
| Widget suite (5+ placements) | XL | 2–4 weeks |
The majority of the time is spent not on the widget itself, but on setting up OAuth authorization, handling edge cases (expired token, portal in a different data center), and testing across multiple browsers — Bitrix24 supports Chrome, Firefox, Safari, and mobile apps with varying iframe behavior.







