Overview
Use BatchV2.make() to execute up to 50 REST API commands in a single request. This is especially useful when you need to retrieve or update large amounts of data while minimizing network requests and adhering to REST API limits.
// Basic usage
import { EnumCrmEntityTypeId } from '@bitrix24/b24jssdk'
const response = await $b24.actions.v2.batch.make({
calls: [
['crm.item.get', { entityTypeId: EnumCrmEntityTypeId.contact, id: 1 }],
['crm.item.get', { entityTypeId: EnumCrmEntityTypeId.contact, id: 2 }]
],
options: {
isHaltOnError: true,
returnAjaxResult: true,
requestId: 'unique-request-id'
}
})
Method Signature
make<T = unknown>(
options: ActionBatchV2
): Promise<CallBatchResult<T>>
Parameters
The options object contains the following properties:
Command Formats (options.calls)
1. Array of tuples (BatchCommandsArrayUniversal)
// @check-ignore: partial snippet — calls array literal, not a valid statement
calls: [
['method1', params1],
['method2', params2],
// ...
]
2. Array of objects (BatchCommandsObjectUniversal)
// @check-ignore: partial snippet — object array literal, not a valid statement
calls: [
{ method: 'method1', params: params1 },
{ method: 'method2', params: params2 },
// ...
]
3. Object with named commands (BatchNamedCommandsUniversal)
calls: {
command1: { method: 'method1', params: params1 },
command2: ['method2', params2],
// ...
}
Batch Request Options (options.options)
options. Passed as top-level properties of the argument they are not applied: TypeScript rejects the literal, and a caller the compiler never sees — plain JavaScript, or a literal widened through a variable — gets a logged warning naming the flag and where it goes. Before that (#426) it was silent, and returnAjaxResult dropped this way is the one that bites: isSuccess on a plain payload is undefined, so a batch where every command succeeded reads as a batch where every command failed.Return Value
The shape is chosen from the arguments, and the overloads say which you get — no cast, no narrowing by hand:
T is one command's payload in every row — not the collection.
params. Write them under query and they are
read by nobody: a v2 command goes on the wire as method?<querystring> built
from params, so it is sent with no arguments at all and the portal answers HTTP
200 with the method's defaults.Measured on user.get with filter: { ACTIVE: 'N' } against a portal whose only
user is active: under params the portal answered 0 rows — the filter did
its work — and the same request spelled query answered 1 row, the active
user the filter was meant to exclude. A wrong answer, not a failure.query is the restApi:v3 wire spelling, which is why it is the one a caller
reaches for; on v2 it is simply a key the parser does not read.TypeScript rejects it in a fresh object literal. It does not see the same literal
assigned to a variable first, or commands built from a config object, a
JSON.parse, or plain JavaScript — so the SDK also warns at run time: one line
per batch request, naming the keys it ignored and the commands that carried them.
It warns only where the arguments were actually lost — a command with no
params, or one naming query. Your own id or label beside a populated
params is left alone.[{ method, params }]) answers by index, like
the tuple form. It is the record form ({ name: { method, params } }) that
answers by name — the dispatch is on the container, not on what the entries look
like.returnAjaxResult is a boolean the compiler cannot read as a literal — a
variable rather than true or false written in place — no overload can decide
for you, and the call resolves to the union
CallBatchResult<T>. That is honest rather than helpful: narrow
it yourself, or pass the flag as a literal.Result Item Data
Each per-command result is returned exactly as the REST API delivered it,
including null when the underlying method legitimately returns no data
(for example, im.chat.get called with an ENTITY_TYPE that does not match
any chat).
const response = await $b24.actions.v2.batch.make<{ ID: number } | null>({
calls: {
chatGet: {
method: 'im.chat.get',
params: { ENTITY_TYPE: 'UNKNOWN', ENTITY_ID: 'UNKNOWN' }
}
},
options: { returnAjaxResult: true, requestId: 'chat-get' }
})
// No cast and no `@check-ignore`: named commands plus `returnAjaxResult: true`
// resolve to a record of `AjaxResult`, and the overloads read that off the
// arguments (#518).
const chatGet = response.getData()!.chatGet!
if (chatGet.getData()?.result === null) {
// method returned null — no chat matched
} else {
console.log(chatGet.getData()?.result.ID)
}
null result was coerced to {}, which broke nullable
type guards on the caller side (see issue #23).
When typing the generic for methods that may return null, declare it as
T | null.Error Handling
Always check the result using isSuccess and handle errors:
// @check-ignore: top-level return in error-handling illustration
const response = await $b24.actions.v2.batch.make({
calls: [
{ method: 'crm.item.get', params: { entityTypeId: EnumCrmEntityTypeId.contact, id: 1 } },
{ method: 'crm.item.get', params: { entityTypeId: EnumCrmEntityTypeId.contact, id: 2 } }
],
options: {
isHaltOnError: true,
returnAjaxResult: true,
requestId: 'unique-request-id'
}
})
if (!response.isSuccess) {
// Handling error
console.error(new Error(`Error: ${response.getErrorMessages().join('; ')}`))
return
}
// Working with a successful result
const data = response.getData()
The isSuccess check covers two distinct failures:
- Per-command errors — the batch envelope succeeds but individual commands fail. Inspect each
AjaxResultingetData(), or usegetErrorsByKey()to see which command failed. - Envelope-level (soft) errors — the whole batch fails before any command result is returned, e.g. a server-side validation code (
BITRIX_REST_V3_EXCEPTION_VALIDATION_*). The outerResultis thenisSuccess === false, the error(s) are ingetErrorMessages()/getErrors(), andgetData().resultis empty. When that code is a validation one, theAjaxErroralso names the field that failed — see Which field failed.
A single if (!response.isSuccess) guard handles both cases.
Examples
Getting Multiple CRM Companies
Init Data Storage
This code automates the creation and initialization of data storages in Bitrix24 via the REST API.
It checks the existence of the specified storages and, if they don't exist, creates them along with the specified properties, using batch requests for efficiency.
Delete Data Storage
This code deletes multiple data storages in the Bitrix24 system via the REST API.
It sequentially sends delete requests for each data storage specified in the dataStorageMap using the entity.delete method.
Alternatives and Recommendations
- For sequential requests: Use
Callfor single calls. - For working with lists: Use
CallListfor retrieving large volumes of data. - For step-by-step processing: Use
FetchListfor processing data as it arrives. - To run more commands (more than 50): Use
BatchByChunk, which automatically splits commands into chunks of 50. - On the client-side (browser): Use the built-in
B24Frameobject.
FetchList
Returns an AsyncGenerator that allows processing data from list methods of Bitrix24 REST API version 2 as it is received without loading the entire array into memory at once. This is especially useful when working with very large volumes of data.
BatchByChunk
Method for executing batch requests with automatic chunking for any number of commands. Automatically splits large command sets into batches of 50 and executes them sequentially. Use only arrays of tuples or arrays of objects.