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:
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
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.
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
bito.i18n(key: string, vars?: Record<string, string | number>): stringLooks the key up in the one locale bundle Bito resolved at mount (see i18n bundles) and substitutes double-brace placeholders.
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
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
bito.settings.save(obj: Record<string, unknown>): Promise<unknown>Persists obj for this tenant's installation of your app.
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
bito.render(tplName: string, data?: Record<string, unknown>): stringRenders 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
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.
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
bito.notify(opts: { message: string, severity?: 'success' | 'error' | 'info' | 'warning' } | string): voidShows 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
bito.confirm(opts: { title?: string, message: string } | string): Promise<boolean>Opens a host confirmation dialog and resolves true or false.
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
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 }
}| Field | Present when |
|---|---|
account_id | The tenant's Bito account, from the mount payload |
user_id | The signed-in user |
subdomain | The tenant's Bito subdomain |
surface | Always — the mount surface you are running on |
entity_id | Profile and card surfaces: the record the screen is showing |
selection | list:<page_name> surfaces only: the rows the user had selected |
phone | card_phone only: the number the user clicked |
lang | Always — the viewer's language |
menu | widget_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
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.