Developing Bitrix24 REST applications with TypeScript

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
    1173
  • 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
    745
  • 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

Developing Bitrix24 REST Applications in TypeScript

The Bitrix24 REST API is feature-rich but convoluted. IDE autocompletion for methods like crm.deal.list, tasks.task.add, and im.message.send only works if you have types. Without TypeScript, developing a Bitrix24 REST application means constantly switching to the documentation and catching undefined errors at runtime.

Developing Bitrix24 REST Applications in TypeScript

Bitrix24 REST Application Architecture

Three types of applications in the Bitrix24 ecosystem:

  1. Web application (iframe) — loads inside the Bitrix24 interface in an iframe. JavaScript/TypeScript with access to the BX24.js SDK.
  2. Server-side application — PHP/Node.js, runs independently, communicates with Bitrix24 via REST over OAuth.
  3. Widget — a compact application in the sidebar or CRM.

TypeScript applies to all three cases, but with different entry points.

Typing the BX24 SDK

There is no official TypeScript package for the BX24 SDK. We write the declaration ourselves:

// types/bx24.d.ts
declare global {
    const BX24: {
        init(callback: () => void): void;
        isAdmin(): boolean;
        getAuth(): BX24Auth;
        refreshAuth(callback: (auth: BX24Auth) => void): void;
        callMethod(
            method:   string,
            params?:  Record<string, unknown>,
            callback?: (result: BX24CallResult) => void
        ): void;
        callBatch(
            calls:    Record<string, [string, Record<string, unknown>?]>,
            callback: (result: Record<string, BX24CallResult>) => void,
            bHaltOnError?: boolean
        ): void;
        resizeWindow(width: number, height: number): void;
        closeApplication(): void;
        placement: {
            info(): BX24PlacementInfo;
            call(command: string, params?: Record<string, unknown>): void;
        };
    };
}

interface BX24Auth {
    access_token:  string;
    refresh_token: string;
    expires_in:    number;
    domain:        string;
    member_id:     string;
}

interface BX24CallResult {
    status():  number;
    data():    unknown;
    error():   string | false;
    more():    boolean;
    next():    void;
    total():   number;
}

interface BX24PlacementInfo {
    placement: string;
    options:   Record<string, string>;
}

export {};

Types for CRM Data

// types/crm.ts

export interface BX24Deal {
    ID:                     string;
    TITLE:                  string;
    STAGE_ID:               string;
    OPPORTUNITY:            string;
    CURRENCY_ID:            string;
    ASSIGNED_BY_ID:         string;
    DATE_CREATE:            string;
    DATE_MODIFY:            string;
    CONTACT_ID:             string | null;
    COMPANY_ID:             string | null;
    COMMENTS:               string | null;
    UF_CRM_CUSTOM_FIELD?:   string; // custom fields via UF_
    [key: string]: unknown; // additional fields
}

export interface BX24Task {
    id:             string;
    title:          string;
    description:    string;
    status:         string;
    responsible:    { id: string; name: string };
    deadline:       string | null;
    createdDate:    string;
    ufTaskWebdavFiles?: string[]; // custom task fields
}

export type StageId =
    | 'NEW' | 'PREPARATION' | 'PREPAYMENT_INVOICE'
    | 'EXECUTING' | 'FINAL_INVOICE' | 'WON' | 'LOSE';

Typed Wrapper Around BX24 callMethod

// api/bx24client.ts

export function callMethod<T>(
    method: string,
    params: Record<string, unknown> = {}
): Promise<T[]> {
    return new Promise((resolve, reject) => {
        const results: T[] = [];

        const handleResult = (result: ReturnType<typeof BX24.callMethod extends (...args: unknown[]) => infer R ? R : never>) => {
            if (result.error()) {
                reject(new Error(String(result.error())));
                return;
            }

            const data = result.data() as T[];
            results.push(...(Array.isArray(data) ? data : [data as T]));

            if (result.more()) {
                result.next(); // automatic pagination
            } else {
                resolve(results);
            }
        };

        BX24.callMethod(method, params, handleResult);
    });
}

// Usage
import type { BX24Deal } from '@/types/crm';

const deals = await callMethod<BX24Deal>('crm.deal.list', {
    filter: { STAGE_ID: 'NEW' },
    select: ['ID', 'TITLE', 'OPPORTUNITY', 'ASSIGNED_BY_ID'],
    order:  { DATE_CREATE: 'DESC' },
});

result.more() + result.next() is the BX24 SDK pagination mechanism. The wrapper automatically traverses all pages and returns the complete array.

Batch Requests for Performance

Each callMethod is a separate HTTP request. For applications with high API load, use callBatch:

export function callBatch<T extends Record<string, unknown>>(
    calls: Record<string, [string, Record<string, unknown>?]>
): Promise<T> {
    return new Promise((resolve, reject) => {
        BX24.callBatch(calls, (results) => {
            const output = {} as T;
            let hasError = false;

            for (const [key, result] of Object.entries(results)) {
                if (result.error()) {
                    hasError = true;
                    console.error(`Batch error for "${key}":`, result.error());
                } else {
                    (output as Record<string, unknown>)[key] = result.data();
                }
            }

            if (hasError) reject(new Error('Batch had errors'));
            else resolve(output);
        });
    });
}

// Load a deal with related data in a single request
const data = await callBatch<{
    deal:    BX24Deal;
    contact: BX24Contact;
    history: BX24Activity[];
}>({
    deal:    ['crm.deal.get',     { id: dealId }],
    contact: ['crm.contact.get',  { id: contactId }],
    history: ['crm.activity.list', { filter: { OWNER_ID: dealId, OWNER_TYPE_ID: '2' } }],
});

React + TypeScript Application in a Bitrix24 iframe

// main.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';

BX24.init(() => {
    const container = document.getElementById('app');
    if (!container) return;

    const root = createRoot(container);
    root.render(<App />);

    // Auto-resize iframe height
    const resizeObserver = new ResizeObserver(() => {
        BX24.resizeWindow(
            document.body.scrollWidth,
            document.body.scrollHeight
        );
    });
    resizeObserver.observe(document.body);
});

Timeline

Task Duration
TypeScript setup, BX24 SDK and CRM entity types 1–2 days
Simple iframe application (view/edit CRM data) 3–5 days
Full-featured React application in Bitrix24 2–4 weeks
Server-side Node.js/TypeScript application with OAuth 1–2 weeks