TypeScript development for the 1C-Bitrix project

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

TypeScript Development for 1C-Bitrix Projects

1C-Bitrix PHP templates generate HTML, while frontend code written in "plain" JavaScript inside a component's template.php or script.js has no types, no autocomplete, and no protection against "undefined is not a function" errors in production. TypeScript is a pragmatic choice for any 1C-Bitrix project where JS code exceeds a few hundred lines.

TypeScript Development for 1C-Bitrix Projects

Where TypeScript lives in a 1C-Bitrix project

Two typical scenarios: TypeScript in the site template and TypeScript in a D7 module.

In the site template:

/local/templates/my_site/
    src/
        ts/
            catalog.ts
            cart.ts
            search.ts
        scss/
            ...
    dist/        <- compiled JS
    package.json
    tsconfig.json
    vite.config.ts

In a module:

/local/modules/mymodule/
    install/
        js/
            src/         <- TypeScript sources
                index.ts
            dist/        <- compiled JS
    package.json
    tsconfig.json

tsconfig.json for a 1C-Bitrix environment

{
    "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "strict": true,
        "noUncheckedIndexedAccess": true,
        "lib": ["ES2020", "DOM"],
        "outDir": "./dist",
        "sourceMap": true,
        "paths": {
            "@/*": ["./src/*"]
        }
    },
    "include": ["src/**/*.ts"],
    "exclude": ["node_modules", "dist"]
}

noUncheckedIndexedAccess: true — strict checking of array index access. Important when working with 1C-Bitrix API results where a field may be absent.

Typing 1C-Bitrix data

The first step is describing the types of data coming from the PHP backend. 1C-Bitrix passes data via JSON in data- attributes or AJAX:

// types/bitrix.ts

export interface BitrixProduct {
    ID: string;
    NAME: string;
    DETAIL_PAGE_URL: string;
    PREVIEW_PICTURE: string | null;
    CATALOG_PRICE_1: string | null;
    CATALOG_CURRENCY_1: string;
    PROPERTY_BRAND_VALUE: string | null;
    PROPERTY_ARTICLE_VALUE: string | null;
}

export interface BitrixCatalogResult {
    ITEMS: BitrixProduct[];
    TOTAL_ITEMS_COUNT: number;
    PAGES_COUNT: number;
    CURRENT_PAGE: number;
}

export interface BitrixAjaxResponse<T = unknown> {
    status: 'success' | 'error';
    data: T;
    errors?: BitrixError[];
}

export interface BitrixError {
    code: string;
    message: string;
    customData?: string;
}

1C-Bitrix returns numeric IDs as strings — "ID": "42", not "ID": 42. This must be reflected in the types.

Working with 1C-Bitrix AJAX responses

// api/catalog.ts
import type { BitrixAjaxResponse, BitrixCatalogResult } from '@/types/bitrix';

export async function fetchCatalogItems(
    sectionId: number,
    page: number,
    filter: Record<string, string[]>
): Promise<BitrixCatalogResult> {
    const params = new URLSearchParams({
        SECTION_ID: String(sectionId),
        PAGE_NUM:   String(page),
        sessid:     BX.bitrix_sessid(), // 1C-Bitrix CSRF token
        action:     'getCatalogItems',
    });

    Object.entries(filter).forEach(([key, values]) => {
        values.forEach(val => params.append(`filter[${key}][]`, val));
    });

    const response = await fetch('/local/ajax/catalog.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body:    params.toString(),
    });

    if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
    }

    const json: BitrixAjaxResponse<BitrixCatalogResult> = await response.json();

    if (json.status !== 'success') {
        throw new Error(json.errors?.[0]?.message ?? 'Unknown error');
    }

    return json.data;
}

BX.bitrix_sessid() — a 1C-Bitrix core method. To let TypeScript know about the global BX object:

// types/globals.d.ts
declare global {
    const BX: {
        bitrix_sessid(): string;
        message(params: Record<string, string>): void;
        bind(el: Element, event: string, fn: (e: Event) => void): void;
        // ... additional methods as needed
    };
}
export {};

Working with DOM in component templates

// Typed access to data attributes
const catalogEl = document.getElementById('catalog-container');
if (!catalogEl) throw new Error('catalog-container not found');

const rawData = catalogEl.dataset['initialData'];
if (!rawData) throw new Error('No initial data');

// Validate via Zod or manual type checking
const initialData: BitrixCatalogResult = JSON.parse(rawData);

Build and integration in the template

Vite compiles TypeScript into /dist/:

// In the template's header.php
$assetManager = \Bitrix\Main\Page\Asset::getInstance();
$assetManager->addJs(SITE_TEMPLATE_PATH . '/dist/catalog.js');

During development — vite dev with HMR; in production — vite build and /dist/ is committed to the repository (or built in CI).

Timelines

Task Timeline
tsconfig + Vite setup for the site template 4–8 hours
Typing existing JS code (≤500 lines) 1–2 days
Developing new functionality directly in TypeScript from 1 day