We are still updating this page. Some data may be missing here — we will complete it shortly.
Goal
Read a CSV mapping of dealId,newStageId and apply the stage change to every row. Could be hundreds of deals or tens of thousands — BatchByChunkV2.make() chunks the calls into 50-per-batch blocks server-side, no manual paging.
Stack & Prerequisites
- Node.js 20+, ESM
@bitrix24/b24jssdk@^1.0.0- A webhook with the
crmscope, set asB24_HOOK. - A CSV file
migrations.csv:dealId,newStageId 1234,WON 1235,WON 1240,LOSE
pnpm add @bitrix24/b24jssdk
export B24_HOOK="https://your-portal.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/"
Full Example
scripts/migrate-deals.mjs:
import { readFile } from 'node:fs/promises'
import {
B24Hook,
EnumCrmEntityTypeId,
ParamsFactory
} from '@bitrix24/b24jssdk'
const hookUrl = process.env.B24_HOOK
if (!hookUrl) {
console.error('B24_HOOK is not set')
process.exit(2)
}
const csv = await readFile(process.argv[2] ?? 'migrations.csv', 'utf8')
const rows = csv
.split(/\r?\n/)
.slice(1) // header
.filter(Boolean)
.map((line) => {
const [dealId, newStageId] = line.split(',')
return { dealId: Number(dealId), newStageId }
})
console.log(`planned updates: ${rows.length}`)
const b24 = B24Hook.fromWebhookUrl(hookUrl, {
restrictionParams: ParamsFactory.getBatchProcessing()
})
const calls = rows.map(({ dealId, newStageId }) => [
'crm.item.update',
{
entityTypeId: EnumCrmEntityTypeId.deal,
id: dealId,
fields: { stageId: newStageId }
}
])
const response = await b24.actions.v2.batchByChunk.make({
calls,
options: {
isHaltOnError: false,
requestId: `deal-stage-migration-${Date.now()}`
}
})
if (!response.isSuccess) {
console.error('partial failure:')
for (const message of response.getErrorMessages()) {
console.error(` - ${message}`)
}
process.exit(1)
}
console.log(`updated: ${response.getData()?.length ?? 0} of ${rows.length}`)
Run It
node scripts/migrate-deals.mjs migrations.csv
# planned updates: 3450
# updated: 3450 of 3450
If a few rows fail (deleted deal, missing scope, invalid stage), isHaltOnError: false lets the rest of the batch finish and the script exits with code 1 after listing the failures.
How It Works
BatchByChunkV2.make({ calls })accepts an arbitrary number of commands and slices them internally into batches of 50, the per-request hard limit. Output order matches input order.Result.isSuccessflips tofalseif any chunk had an error.getErrorMessages()returns one message per failed call. Successful calls are still ingetData()— partial progress is preserved.ParamsFactory.getBatchProcessing()raises the operating-time threshold and lowers the request rate, which keeps the portal queue responsive while a long migration runs alongside live UI traffic.- Naming the request via
options.requestIdmakes it easy to filter portal-side logs and to deduplicate accidental re-runs.
Limitations
- The two-tuple
callsform is required:BatchByChunkV2deliberately rejects named-object commands because chunk boundaries would split the names apart. - Each command counts against the daily method-call budget. For tens of thousands of updates, schedule the script to run during off-peak hours and consider splitting by responsible user.
- Bitrix24's
crm.item.updatevalidatesstageIdagainst the deal's category. A typo innewStageIdreturns a per-call error — visible ingetErrorMessages()— but the whole script does not abort thanks toisHaltOnError: false. - Atomic rollback is not supported. If you need transactional behavior, dump current values into a separate CSV first and run a second migration to restore.
See also
BatchByChunkV2.make()— full method reference.BatchV2.make()— when you have ≤ 50 calls and want named results.- Restrictions System — choosing a
restrictionParamspreset.
Webhook CLI
A 30-line Node script that authenticates against a Bitrix24 portal via inbound webhook and prints the calling user — useful as the first thing you run after creating a webhook.
ERP sync
Two-way contact sync between Bitrix24 and an external ERP. Match by INN (primary) or email (fallback). Cron every hour.