v2.1.0

App Installation Wizard

Multi-step install flow that creates user fields, registers placements, celebrates with confetti, and finalises the installation last — because the portal reloads the page in response.
Required scopes depend on what the wizard does — at minimum crm, user_brief, userfieldconfig, placement.

A Bitrix24 application is "installing" between when the user accepts the install dialog and when the app calls installFinish(). During that window you can provision custom fields, register placements, seed app options — anything the app needs before it greets the user. This recipe shows the structure of such a flow.

Components Used

Flow

import { initializeB24Frame, type B24Frame } from '@bitrix24/b24jssdk'

let $b24: B24Frame

async function provision(): Promise<void> {
  // Wrap related steps in a batch so they hit a single round-trip
  await $b24.actions.v2.batch.make({
    calls: {
      addUserField: {
        method: 'userfieldconfig.add',
        params: {
          moduleId: 'crm',
          field: { ENTITY_ID: 'CRM_DEAL', FIELD_NAME: 'UF_CRM_DEAL_DEMO', USER_TYPE_ID: 'string' }
        }
      },
      bindPlacement: {
        method: 'placement.bind',
        params: { PLACEMENT: 'CRM_DEAL_LIST_MENU', HANDLER: '/embedded/menu' }
      }
    },
    options: {
      isHaltOnError: true,
      requestId: 'install:provision'
    }
  })
}

async function run() {
  $b24 = await initializeB24Frame()

  if (!$b24.isInstallMode) {
    // Already installed — render the regular UI
    return
  }

  await provision()

  // Celebrate BEFORE finalising: the portal reloads the page in response to
  // installFinish(), so anything after it may never run.
  // celebrate with useConfetti() ...

  await $b24.installFinish()
}

run().catch(console.error)

installFinish()'s promise rejects with an SdkErrorcode: 'JSSDK_FRAME_INSTALL_ALREADY_FINISHED' — when the application is no longer in install mode; guard with isInstallMode.

Never skipping installFinish() is the whole point of this recipe. A wizard-style app that does not finalise its installation stays half-installed, and the portal does not deliver events to it: handlers bound with event.bind never fire and placements bound with placement.bind never appear, even though both registration calls returned success. This is Bitrix24's documented behaviour.The failure is silent and misleading: registration succeeds, imbot.register hands back a botId, the handlers are listed when you query them, and nothing ever arrives. Developers routinely lose days on nginx, tunnels and TLS before finding out the application was never finished installing.The opposite case is worth knowing, because it is the mirror mistake: an API-only app with no installation interface must not call installFinish(). Its installation completes automatically, and the method works only inside a browser interface frame — so this recipe is for apps that have an install screen, not for headless ones.

"My handlers never fire"

Before checking anything on your side of the wire, ask the portal whether it considers the application installed:

const response = await $b24.actions.v2.call.make<{ INSTALLED: boolean }>({
  method: 'app.info',
  requestId: 'app-info-1'
})
console.log(response.getData()!.result.INSTALLED)

INSTALLED: false means installFinish() never landed, and no amount of debugging your endpoint will help until it does. true means the installation is complete and the problem is somewhere else.

Full Source

See bitrix24/b24sdk-examples for the complete wizard with progress UI, retry, and confetti.