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 |







