The useB24Helper() composable wraps B24HelperManager and the Pull client lifecycle. It's the recommended way to load portal-wide context (currencies, your app's install info, the current user) once per app boot.
Initial setup
import {
initializeB24Frame,
useB24Helper,
LoadDataType,
type TypePullMessage
} from '@bitrix24/b24jssdk'
const { initB24Helper, isInitB24Helper, destroyB24Helper, getB24Helper, usePullClient, useSubscribePullClient, startPullClient } = useB24Helper()
const $b24 = await initializeB24Frame()
await initB24Helper( $b24, [ LoadDataType.Profile, LoadDataType.App, LoadDataType.Currency, LoadDataType.AppOptions, LoadDataType.UserOptions ], /* requestId */ 'app-boot' )
const helper = getB24Helper()
Pass only the `LoadDataType` flags you actually need — `initB24Helper` issues a single batched REST call (`$b24.actions.v2.batch.make` internally) and each flag adds work to it. `Profile` and `App` (the defaults) are usually the minimum.
`isInitB24Helper()` returns `true` once `initB24Helper` resolved — useful for guards.
## Reading helper data
```ts
const me = helper.profileInfo.data
// { id, name, lastName, isAdmin, … }
const app = helper.appInfo.data
const status = helper.appInfo.statusCode // EnumAppStatus
const license = helper.licenseInfo // for enterprise checks
const payment = helper.paymentInfo
const baseCurrency = helper.currency.baseCurrency
Accessing helper.profileInfo before initB24Helper resolves throws B24HelperManager.profileInfo not initialized. Use isInitB24Helper() as a guard if the load is asynchronous w.r.t. your UI.
Currency formatting
const symbol = helper.currency.getCurrencyLiteral('USD', 'en') // '$'
const full = helper.currency.getCurrencyFullName('USD', 'en') // 'US Dollar'
const pretty = helper.currency.format(1234.56, 'USD', 'en') // '$1,234.56'
format honours per-currency rules (decimal places, separators) loaded from the portal.
Bulk options with notification
The helper-level OptionsManager can save many keys in one batch and broadcast a Pull event:
await helper.appOptions.save(
{
featureFlags: helper.appOptions.encode({ exportCsv: true, beta: false }),
lastSyncAt: new Date().toISOString()
},
{
moduleId: 'application',
command: 'FEATURES_UPDATED',
params: { source: 'app' }
}
)
// `getJsonObject` returns `object` — it takes no type argument. Cast at the
// callsite if you want a shape.
const flags = helper.appOptions.getJsonObject(
'featureFlags',
{ exportCsv: false, beta: false }
)
encode JSON-stringifies into a Bitrix24-compatible string; getJsonObject reverses it with a typed default.
Pull client (push events)
import type { TypePullMessage } from '@bitrix24/b24jssdk'
usePullClient()
useSubscribePullClient((m: TypePullMessage) => {
if (m.command === 'FEATURES_UPDATED') {
// re-read options, update UI
}
}, /* moduleId */ 'application')
startPullClient()
Subscribe before
startPullClient()— the client connects after start.useSubscribePullClientandstartPullClientthrow ifusePullClientwas not called first.
Publish from the back end, not from the Pull client
To put a message into the channel, call pull.application.event.add over REST
— MODULE_ID is what your subscribers filter on:
import type { B24Frame } from '@bitrix24/b24jssdk'
declare const $b24: B24Frame
await $b24.actions.v2.call.make({
method: 'pull.application.event.add',
params: {
COMMAND: 'FEATURES_UPDATED',
PARAMS: { source: 'app' },
MODULE_ID: 'application'
}
})
PullClient does expose sendMessage(), but it needs pull.channel.public.list
to resolve the recipients' channels and that method is not in the application
REST surface — it usually rejects with JSSDK_PULL_PUBLIC_IDS_UNAVAILABLE. Do
not generate code that calls it from an app.
"Usually" because the lookup is skipped when every recipient already has an
unexpired cached channel, which the startup config call prefills from
publicChannels — so a send to the current user can go out without rejecting.
That is not a reason to use it: the send is still not a delivery receipt, and
what comes back arrives on a SubscriptionType.Client subscription rather than
the Server one subscribe() defaults to.
Nothing in the SDK gates publishing on being an application, either —
isPublishingEnabled() reads only serverVersion > 3 && publish_enabled. And
sendMessageToChannels() takes channel ids from the caller, so it never makes
the lookup and never raises that code at all. The guidance above is about which
path is SUPPORTED, not about which one the code permits.
Multiple subscriptions on different moduleIds are supported — call useSubscribePullClient once per channel:
useSubscribePullClient(handleAppEvent, 'application')
useSubscribePullClient(handleImEvent, 'im')
useSubscribePullClient(handleCrmEvent, 'crm')
To shut down (also tears the Pull client down):
import { useB24Helper } from '@bitrix24/b24jssdk'
const { destroyB24Helper } = useB24Helper()
destroyB24Helper()
Using helpers with B24Hook / B24OAuth
B24HelperManager itself works with any TypeB24. The composable is designed around the in-frame lifecycle, but for backend code you can construct the manager directly:
import { B24HelperManager, LoadDataType } from '@bitrix24/b24jssdk'
const helper = new B24HelperManager($b24)
await helper.loadData([LoadDataType.Profile, LoadDataType.Currency])
const baseCurrency = helper.currency.baseCurrency
Pull is only meaningful in an interactive context. Don't startPullClient() from a CLI script.
Anti-patterns
- ❌ Calling
initB24Helpertwice withoutdestroyB24Helperin between — the second call is a no-op (early-return on$isInitB24Helper), the data does NOT refresh. - ❌
usePullClientwithoutuseSubscribePullClient— connection runs but nothing handles messages. - ❌ Loading every
LoadDataType"just in case" — every extra flag costs REST work in the batch. - ❌ Reading
helper.profileInfo.databeforeinitB24Helperresolves.