Skip to content

The bito SDK ​

Your widget gets one global: window.bito. It exists so a well-behaved widget does not have to hand-roll auth, translation and host chrome.

WARNING

bito is a convenience layer, not a security boundary. Your code runs in the admin panel's own origin and session — it can call fetch directly, read the ERP's DOM, and reach its storage. Nothing in bito.* contains a widget that chooses not to use it. The control is the tenant's consent screen, not this object.

Getting hold of it ​

window.bito is installed when your widget mounts and restored to its previous value when it unmounts. If a second widget mounts while yours is still running, window.bito points at the newer one until that one tears down.

Capture it once, early, and use your own reference:

js
define(['jquery'], function ($) {
  return function CustomWidget() {
    var api = null
    this.callbacks = {
      render: function (container) {
        api = window.bito           // capture, do not re-read later
        container.innerHTML = '<div class="acme-root"></div>'
        return true
      },
    }
  }
})

bito.ajax ​

ts
bito.ajax(url: string, data?: object): Promise<unknown>

POSTs to the admin API as the signed-in user and resolves the unwrapped payload — the data of the response envelope, not the envelope. A url that does not already start with the admin prefix is prefixed with it, so 'customer/get-by-id' and /back-api/admin/customer/get-by-id are the same call.

js
bito.ajax('customer/get-by-id', { _id: bito.context.entity_id })
  .then(function (customer) { console.log(customer.name) })

Failure mode: rejects. The request rides the admin panel's shared HTTP client, so it carries the same auth, idempotency and 401 handling as the rest of the app — an expired session is handled by the app, not by you.

INFO

There is no third opts argument. Every call is a POST; there is no way to ask this method for another method.

bito.i18n ​

ts
bito.i18n(key: string, vars?: Record<string, string | number>): string

Looks the key up in the one locale bundle Bito resolved at mount (see i18n bundles) and substitutes double-brace placeholders.

js
bito.i18n('greeting', { name: 'Aziz' })

Failure mode: never throws. A missing key returns the key itself, and a placeholder with no matching variable is left in the string untouched. If a user sees a raw key in your UI, that language's bundle is missing that key.

bito.settings.get ​

ts
bito.settings.get(): Record<string, unknown>

Returns the installation settings for this tenant, exactly as they were handed to your widget at mount. Synchronous, no request. Secrets are excluded server-side before the payload ever reaches the browser.

Failure mode: none — it is a snapshot. It does not re-read after a save, so keep your own copy of what you just wrote if you need it within the same mount.

bito.settings.save ​

ts
bito.settings.save(obj: Record<string, unknown>): Promise<unknown>

Persists obj for this tenant's installation of your app.

js
bito.settings.save({ api_token: 'xxx', region: 'tashkent' })

Failure mode: rejects. Consent is re-checked on every call, the same way mount checks it — if the tenant revoked consent, or a newer release was approved and not yet consented to, the save is refused. The object is stored verbatim in your installation's settings and nowhere else: it can never widen your integration's role, scopes or field access.

bito.render ​

ts
bito.render(tplName: string, data?: Record<string, unknown>): string

Renders a templates/ entry by its name after the prefix (templates/card.twig → 'card.twig'), substituting double-brace placeholders. Returns a string; it does not touch the DOM.

Failure mode: an unknown template name returns the empty string, silently. If your widget renders blank, check the name against what is actually in the archive.

bito.modal ​

ts
bito.modal(opts: { title?: string, content: string | Node }): { close(): void }

Opens a host modal. A string content is injected as HTML; a Node is appended as-is. The returned handle closes it.

js
var m = bito.modal({ title: bito.i18n('title'), content: '<p>Hi</p>' })
setTimeout(function () { m.close() }, 3000)

Failure mode: nothing rejects. Content you pass as a string is inserted as markup — everything in it runs with the page's privileges, so never build it from data you did not sanitize.

bito.notify ​

ts
bito.notify(opts: { message: string, severity?: 'success' | 'error' | 'info' | 'warning' } | string): void

Shows a host toast. A bare string is treated as { message }.

Failure mode: none, and note the granularity: only severity: 'error' renders as an error toast. success, info, warning and an omitted severity all render as a success toast.

bito.confirm ​

ts
bito.confirm(opts: { title?: string, message: string } | string): Promise<boolean>

Opens a host confirmation dialog and resolves true or false.

js
bito.confirm(bito.i18n('really_delete')).then(function (yes) {
  if (yes) { /* ... */ }
})

Failure mode: never rejects. It resolves only once the user answers — do not race it against your own timeout and assume a default.

bito.context ​

ts
bito.context: {
  account_id?: string
  user_id?: string
  subdomain?: string
  surface: string
  entity_id?: string
  selection?: { ids: Array<string | number>, isAll: boolean, page_name: string }
  phone?: string
  lang: string
  menu?: { location: string, item_code: string, subitem_code: string | null }
}
FieldPresent when
account_idThe tenant's Bito account, from the mount payload
user_idThe signed-in user
subdomainThe tenant's Bito subdomain
surfaceAlways — the mount surface you are running on
entity_idProfile and card surfaces: the record the screen is showing
selectionlist:<page_name> surfaces only: the rows the user had selected
phonecard_phone only: the number the user clicked
langAlways — the viewer's language
menuwidget_page only: the open left-menu page — location (widget_page or a module key), item_code (your widget code, or client_id without one), subitem_code (the submenu item, or null for the section's own page). Updated before every initMenuPage call

Failure mode: read it, do not assume it. Everything except surface and lang is optional, and which fields are populated depends entirely on the surface.

bito.widget ​

ts
bito.widget: { client_id: string, code?: string, version?: string }

Identifies your app to itself. client_id and version are always populated — version is the semver of the release you are actually mounted from, read from the release record rather than the manifest, which carries no version field.

INFO

code is your app's widget code — the system_id you claim in My apps, the same value as {widget_code} in left-menu URLs. It is filled on every surface and is absent only if the app has no widget code.

Bito 2.0 Integration API