v1.2.0

Recipe: Export deals to CSV

Stream every CRM deal that matches a date filter into a CSV file using a webhook and FetchListV2.
We are still updating this page. Some data may be missing here — we will complete it shortly.

Goal

Pull every CRM deal closed in a date window from a Bitrix24 portal and write it into a CSV file. Memory stays flat regardless of how many deals match — FetchListV2.make() streams pages of 50 records at a time.

Stack & Prerequisites

  • Node.js 20+, ESM ("type": "module" in package.json)
  • @bitrix24/b24jssdk@^1.0.0
  • A Bitrix24 inbound webhook URL with the crm scope. Set it as B24_HOOK in your environment.
pnpm add @bitrix24/b24jssdk
export B24_HOOK="https://your-portal.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/"

Full Example

scripts/deals-to-csv.mjs:

import { writeFile } from 'node:fs/promises'
import {
  B24Hook,
  EnumCrmEntityTypeId,
  ParamsFactory,
  Text
} from '@bitrix24/b24jssdk'

const hookUrl = process.env.B24_HOOK
if (!hookUrl) {
  console.error('B24_HOOK is not set')
  process.exit(1)
}

// Closed in the previous calendar month.
const now = new Date()
const from = new Date(now.getFullYear(), now.getMonth() - 1, 1)
const to = new Date(now.getFullYear(), now.getMonth(), 1)

const b24 = B24Hook.fromWebhookUrl(hookUrl, {
  // Lower per-second pressure than `getDefault()` — good for reporting jobs
  // that run alongside live user traffic on the same portal.
  restrictionParams: ParamsFactory.getBatchProcessing()
})

const generator = b24.actions.v2.fetchList.make({
  method: 'crm.item.list',
  params: {
    entityTypeId: EnumCrmEntityTypeId.deal,
    filter: {
      '>=closedAt': Text.toB24Format(from),
      '<closedAt': Text.toB24Format(to),
      stageSemanticId: 'S' // closed-won
    },
    select: ['id', 'title', 'opportunity', 'currencyId', 'closedAt', 'assignedById']
  },
  idKey: 'id',
  customKeyForResult: 'items',
  requestId: 'deals-to-csv'
})

const rows = ['id;title;amount;currency;closedAt;assignedById']
let count = 0
for await (const chunk of generator) {
  for (const deal of chunk) {
    rows.push([
      deal.id,
      JSON.stringify(deal.title ?? ''),
      deal.opportunity ?? '',
      deal.currencyId ?? '',
      deal.closedAt ?? '',
      deal.assignedById ?? ''
    ].join(';'))
  }
  count += chunk.length
  process.stderr.write(`fetched: ${count}\r`)
}

const file = `deals-${from.toISOString().slice(0, 7)}.csv`
await writeFile(file, rows.join('\n'), 'utf8')
console.log(`\nwrote ${count} rows to ${file}`)

Run It

node scripts/deals-to-csv.mjs
# fetched: 4350
# wrote 4350 rows to deals-2026-04.csv

How It Works

  • B24Hook.fromWebhookUrl() parses the webhook URL and gives you a configured TypeB24 instance — no await initialization needed.
  • ParamsFactory.getBatchProcessing() swaps the default rate-limit profile for one tuned for long-running scans, so the script doesn't fight live UI requests for the portal queue.
  • actions.v2.fetchList.make() returns an AsyncGenerator<T[]>. Each for await iteration yields up to limit records (default 50) and uses cursor-based pagination on idKey ascending — the SDK takes care of stitching pages together.
  • Text.toB24Format(date) converts a JS Date to the ISO format Bitrix24 expects in crm.item.list filters; passing a raw Date would not work.

Limitations

  • crm.item.list returns up to 50 records per page; this is a server-side limit, not an SDK setting.
  • The generator does not support a custom order parameter — cursor pagination requires sorting by idKey ascending. Specifying order in params triggers a runtime warning and is stripped.
  • idKey defaults to 'ID' (uppercase) for v2 endpoints. CRM item methods return id (lowercase), so override it explicitly as in the example.
  • One B24Hook instance corresponds to a single user account on the portal. If the deals you need are owned by another user, the webhook must be created by that user (or by an account with cross-user access).
  • For very large windows that exhaust the per-day method-call budget, split the date range into smaller chunks and run them sequentially.

See also