umbot
    Preparing search index...

    umbot API reference

    This page is machine-translated from the Russian original. If something reads oddly, the Russian version is the source of truth — open an issue.

    This reference describes the main public classes, methods and interfaces of the umbot framework. To get started, see the "Quick start" section.

    The main class for managing the application logic. It provides the base functionality for handling user requests, managing state and interacting with different platforms.

    Property Type Description
    text string The response text for the user
    tts string | null Text for speech (on voice platforms, if null, it can be filled automatically from text)
    buttons Buttons The buttons component (initialized lazily through a getter)
    card Card The cards/galleries component (initialized lazily through a getter)
    nlu Nlu NLU data (initialized lazily through a getter)
    sound Sound Sound effects (initialized lazily through a getter)
    userId string | number | null The user identifier
    userToken string | null The user authorization token (if the platform provides it)
    userMeta unknown | null Additional user information (platform-dependent)
    messageId number | string | null The message number. 0 is the start of a new session on voice platforms (welcome is used for it)
    userCommand string | null The user command in lower case
    originalUserCommand string | null The original user command
    payload Record<string, unknown> | string | null | undefined Additional request parameters (payload)
    eventType TEventType The universal event type ('message', 'photo', 'callback', 'start', …). Filled by the platform adapter; the basis of bot.addEvent routing
    match RegExpExecArray | null The command's regex match (lazy): groups in match[1], match.groups. null for string commands
    api IControllerApi | null The active platform's API facade: sendPhoto/sendDocument/sendAudio/sendVideo/answerCallback/can. A lazy object; null on voice platforms
    userData TUserData User data (the database or local storage, depending on setAppConfig)
    state TPlatformState | null The platform's local storage (if the platform supports it and isLocalStorage is enabled)
    isAuth boolean Request user authorization (account linking; Alice)
    userEvents IUserEvent | null User events (authorization/rating), if the platform sends them
    isScreen boolean Whether the user has a screen (if the platform reports it)
    isEnd boolean End the session. Read by voice platforms; chat platforms ignore the flag
    skipAutoReply boolean If true, the framework does not try to "auto-send" the response (relevant for platforms where you send messages via the API yourself)
    requestObject Record<string, unknown> | string | unknown | null The original request object from the platform
    thisIntentName string | null The step/intent name to save as the "next step"
    oldIntentName string | null The name of the previous step/intent (from userData.oldIntentName or state.oldIntentName)
    emotion string | null The assistant's response emotion (SmartApp: 'radost', 'pechal', …)
    appeal 'official' | 'no_official' | null The form of address (SmartApp; arrives in the request)
    isSendRating boolean Request a skill rating (the special response is sent only on SmartApp)
    appContext AppContext The application context (config, registries, logger)
    appType TAppType | null The platform the request came from (filled by the framework during processing)
    platformOptions IPlatformOptions Service request data from the adapter, the core and middleware: clientIp, requestId, rateLimitOverflow, etc. Read it; write only in your own adapter/middleware
    Method Parameters Return value Description
    action intentName: string | null, isCommand?: boolean, isStep?: boolean void | Promise<void> Your main handler. Called by the framework (overridden in a subclass); can be async — the framework awaits the promise
    run - void | Promise Starts request processing (called by the framework; usually not called manually)
    setAppContext appContext: AppContext this Sets the application context (updates the context in the already created buttons and card components)
    clearStoreData - void Fully resets the controller state: text, tts, user data, flags and the NLU cache
    isButtonsInit - boolean true if the buttons component was initialized (through the buttons getter)
    isCardInit - boolean true if the cards component was initialized (through the card getter)
    isSoundInit - boolean true if the sounds component was initialized (through the sound getter)
    isNluInit - boolean true if the NLU component was initialized (through the nlu getter)

    The main orchestrator class. It manages the lifecycle, middleware, command registration and starting the server.

    class Bot<
    TUserData extends IUserData = IUserData,
    TPlatformState extends IPlatformData = IPlatformData,
    > {
    constructor(type?: TAppType, botController?: TBotControllerClass<TUserData, TPlatformState>);
    }

    type is the default platform (usually not needed: the platform is detected from the request), botController is the controller class (the same as initBotController). All methods except those listed below with a different return value return this — calls can be chained.

    Method Parameters Return value Description
    setAppConfig config: Partial<IAppConfig> Bot Sets the application configuration
    setAppMode mode: TAppMode ('dev' | 'prod' | 'strict_prod') Bot Sets the operating mode
    setPlatformParams params: IAppParam Bot Sets the platform parameters
    initBotController controller: TBotControllerClass Bot Connects the controller class
    addCommand commandName: string, slots: TSlots, cb: (userCommand: string, bc: BotController) => void | string | Promise<void | string>, isPattern?: boolean Bot Registers a command
    addAction actionName: string, cb: (userCommand: string, bc: BotController) => void | string | Promise<void | string> Bot A callback button press handler by payload (like bot.action() in Telegram frameworks). The payload 'buy' / {"command":"buy"} calls the buy command (Telegram, VK, MAX)
    addEvent eventType: TEventType, cb: (bc: BotController) => void | string | Promise<void | string> | false Bot A platform event handler (a photo, a voice message, callback, start, etc.). Called before steps and commands; false passes the request on to the regular pipeline
    removeEvent eventType: TEventType Bot Removes all handlers of an event
    clearEvents - Bot Removes all event handlers
    removeCommand commandName: string Bot Removes a command by name
    clearCommands - Bot Removes all commands
    addStep stepName: string, handler: IStepParam['cb'] Bot Registers a step (a dialog chain)
    removeStep stepName: string Bot Removes a step by name
    clearSteps - Bot Removes all steps
    addForm formName: string, options: IAddFormOptions Bot Registers a multi-step form with field validation
    removeForm formName: string Bot Removes a form and all its steps
    use fn: MiddlewareFn | platform: TAppType, fn: MiddlewareFn | plugin: TPlugin Bot Connects middleware or a plugin
    clearUse - Bot Removes all platforms, plugins and middleware
    setCustomCommandResolver resolver: TCommandResolver Bot Sets a custom command resolver
    setCommandGroupMode mode: TCommandGroupMode Bot The RegExp grouping mode
    setPlatformResolver resolver: TPlatformResolver Bot Sets the platform detection function
    setLogger logger: ILogger | null Bot Sets a custom logger (null disables it)
    getAppContext - AppContext Gets the application context
    setContent content: TBotContent (object | string | null) void Sets the request content (for testing)
    run appType?: TAppType | null, content?: string | object | null, auth?: TBotAuth, clientIp?: string Promise<TRunResult> Processes an incoming request. All parameters have defaults (null), so a call without arguments is valid. clientIp is available to middleware via controller.platformOptions.clientIp (for example, ipFilter)
    webhookHandle req: IncomingMessage, res: ServerResponse, responseCb?: TBotResponseCb Promise<void> An HTTP request handler (for Express/Fastify integration)
    webhookEvent data: string | object | null, headers?: Record<string, unknown>, clientIp?: string Promise<IWebhookEventResult> Processes a serverless platform event (Yandex Cloud Functions, AWS Lambda) with webhook signature verification. Returns { statusCode, body } to return from the cloud function
    start hostname?: string, port?: number, responseCb?: TBotResponseCb Server Starts the HTTP server (returns a Server instance)
    startPolling options?: IPollingOptions ({ platforms?: TAppType[] }) Promise<void> Starts long polling (Telegram, VK, MAX) instead of a webhook. The promise resolves after stopping; it rejects if no adapter supports polling
    stopPolling - Promise<void> Stops long polling: aborts the current update requests and waits for the already received ones to be processed
    close - Promise<void> Stops the HTTP server and long polling, cleans up resources
    send userId: string | number, controllerOrText: BotController | string, platform: TAppType Promise<unknown | boolean> Sends a message to a user (for platforms that support it)
    import { Bot } from 'umbot';
    import { fullPlatforms, FileAdapter } from 'umbot/plugins';

    const bot = new Bot();
    bot.setAppMode('strict_prod'); // 0. The production mode — before registering commands and parameters
    bot.use(fullPlatforms); // 1. Register the platforms
    bot.use(new FileAdapter()); // 2. Register the database (or isLocalStorage: true)
    bot.setAppConfig({
    // 3. Pass the config
    json: './data',
    error_log: './errors',
    isLocalStorage: false,
    });
    bot.setPlatformParams({
    intents: [{ name: 'bye', slots: ['bye'] }], // a required field (can be [])
    welcome_text: 'Hi!',
    });
    bot.start('0.0.0.0', 3000); // 4. Start

    The controller (initBotController) and commands (addCommand) are optional for starting. Without them the bot replies only with welcome_text / help_text / empty_text. This is handy for the very first start — to make sure the webhook works, and then add logic gradually.

    If you want it even shorter, there is the run utility:

    import { run } from 'umbot/build'; // TMode = 'dev' | 'dev-online' | 'prod'
    import { fullPlatforms, FileAdapter } from 'umbot/plugins';
    import { MyController } from './controller/MyController';

    run(
    {
    appConfig: { isLocalStorage: true },
    appParam: { intents: [{ name: 'bye', slots: ['bye'] }] },
    controller: MyController,
    plugins: [fullPlatforms, new FileAdapter()],
    logic: (bot) => {
    bot.addCommand('ping', ['ping'], (_, bc) => {
    bc.text = 'pong';
    });
    },
    },
    'prod',
    '0.0.0.0',
    8080,
    );
    // run(config, mode: TMode = 'prod', hostname = 'localhost', port = 3000)
    // → 'dev' (runs BotTest.test()), 'dev-online' (a server in the dev mode), 'prod' (a server in strict_prod)

    If plugins is not passed, run connects all platforms (fullPlatforms) and a database adapter: MongoAdapter when a database address is set (appConfig.db.host or DB_HOST), otherwise FileAdapter.

    The component for working with interface buttons.

    Method Parameters Return value Description
    addBtn title: string | null, url?: string | null, payload?: TButtonPayload, options?: IButtonOptions this Adds a button
    addLink title: string | null, url?: string, payload?: TButtonPayload, options?: IButtonOptions this Adds a link button
    row - this Ends a row: the next buttons go to a new line (Telegram, VK, MAX, Viber). The buttons before the first call form the first row; without a call each button is on its own line
    getButtons buttonProcessing: TButtonProcessing T | null Gets the array of buttons adapted to the platform
    getButtonJson buttonProcessing: TButtonProcessing string | null The JSON representation of the buttons for the platform
    clear - void Clears all buttons (starts the list over; an already shown keyboard is not removed)
    remove - this Asks the platform to remove a previously shown keyboard. Relevant for Telegram (the reply keyboard) and VK, where the keyboard "sticks" to the dialog; on other platforms the call is safe and changes nothing
    isRemove - boolean (a getter) true if remove() was called and the keyboard needs to be removed

    The component for working with cards and galleries.

    Method Parameters Return value Description
    addImage image: string | null, title?: string, desc?: string, button?: TButton | null this Adds an image/element (the 4th parameter is the element's button)
    addOneImage image: string | null, title?: string, desc?: string, button?: TButton | null this Replaces the current card with a single image
    setTitle text: string this Sets the title (overwrites the previous one)
    setDescription text: string this Sets the description (overwrites the previous one)
    addButton button: TButton this Adds a button to a card element
    clear - void Clears the card: images, title, description and template

    The component for working with sounds. It supports the platforms' standard sounds (Alice, Marusia) and custom audio files.

    Property Type Description
    sounds ISound[] An array of custom sounds
    isUsedStandardSound boolean Use the platform's standard sounds (true by default)
    Method Parameters Return value Description
    getSounds text: string | null, soundProcessing: TSoundProcessing<TResult>, controller: BotController Promise<TResult> Gets the text with embedded sounds for the platform

    The component for working with entities the platform (or a plugin) extracted from the user's text. It is available in the controller as this.nlu (initialized lazily). The data is filled by the platform adapter from the request (for example, Alice sends it in request.nlu); if the platform does not provide NLU, the data can be filled manually with setNlu().

    All entity extraction methods return an object of the same shape:

    interface INluResult<T = object> {
    status: boolean; // whether at least one value was found
    result: T | null; // the found values (null if nothing was found)
    }
    Constant Value Description
    Nlu.T_FIO 'YANDEX.FIO' Full name
    Nlu.T_GEO 'YANDEX.GEO' Geolocation
    Nlu.T_DATETIME 'YANDEX.DATETIME' Date and time
    Nlu.T_NUMBER 'YANDEX.NUMBER' A number
    Nlu.T_INTENT_CONFIRM 'YANDEX.CONFIRM' The consent intent
    Nlu.T_INTENT_REJECT 'YANDEX.REJECT' The refusal intent
    Nlu.T_INTENT_HELP 'YANDEX.HELP' The help intent
    Nlu.T_INTENT_REPEAT 'YANDEX.REPEAT' The repeat intent
    Method Parameters Return value Description
    getFio - INluResult<INluFIO[]> The full name from the text (first_name, last_name, patronymic_name)
    getGeo - INluResult<INluGeo[]> Geolocation (country, city, street, house_number, airport, etc.)
    getDateTime - INluResult<INluDateTime[]> Date and time (year, month, day, hour, minute + the *_is_relative flags)
    getNumber - INluResult<number[]> Numbers from the text
    getUserName - INluThisUser | null Information about the current user (first_name, last_name, username), if the platform sent it
    isIntentConfirm userCommand?: string boolean Checks the consent intent; if there is no intent and a text is passed, an extra check by consent words (Russian "yes", "of course", etc.)
    isIntentReject userCommand?: string boolean Checks the refusal intent; a similar fallback check by refusal words (Russian "no", "I don't want to", etc.)
    isIntentHelp - boolean Checks the help intent
    isIntentRepeat - boolean Checks the repeat intent
    getIntents - INluIntents | null All intents of the request
    getIntent intentName: string INluIntent | null A specific intent by name (for example, 'YANDEX.CONFIRM')
    getNluValue - INlu The raw NLU object
    setNlu nlu: INlu, isClearCache?: boolean void Sets the NLU data; isClearCache: true resets the cache of extracted entities

    They work with arbitrary text and need no data from the platform:

    Method Parameters Return value Description
    Nlu.getLink query: string INluResult<string[] | null> Extracts links from the text
    Nlu.getPhone query: string INluResult<string[] | null> Extracts phone numbers
    Nlu.getEMail query: string INluResult<string[] | null> Extracts email addresses
    const fio = this.nlu.getFio();
    if (fio.status) {
    this.text = `Nice to meet you, ${fio.result?.[0]?.first_name}!`;
    }

    // Static methods — for text without platform data
    const phones = Nlu.getPhone(this.userCommand || '');
    if (phones.status) {
    this.userData.phone = phones.result?.[0];
    }

    The application configuration.

    interface IAppConfig {
    error_log?: string; // The path to the logs directory
    json?: string; // The path to the JSON directory
    db?: IAppDB; // The database configuration
    isLocalStorage?: boolean; // Use local storage
    memorySession?: IMemorySessionConfig | false; // An in-process userData session (platforms without localStorage, without a database)
    env?: string; // The path to the .env file or 'local' for process.env
    tokens?: ITokenPlatform; // Platform tokens (telegram, vk, etc.)
    }

    The application parameters.

    interface IAppParam {
    isAuthUser?: boolean; // Whether user authorization is required
    welcome_text?: string | string[]; // The greeting text
    help_text?: string | string[]; // The help text
    empty_text?: string | string[]; // The text when no command matches
    intents: IAppIntent[] | null; // The array of intents
    utm_text?: string | null; // The UTM tag for links
    }

    The interface for storing user data.

    interface IUserData {
    oldIntentName?: string | null; // The name of the previous intent (null on reset)
    [key: string]: unknown; // Additional data
    }
    const WELCOME_INTENT_NAME = 'welcome'; // The greeting intent
    const HELP_INTENT_NAME = 'help'; // The help intent
    const FALLBACK_COMMAND = '*'; // The fallback command (called when nothing matches)

    ⚠️ bot.addCommand(FALLBACK_COMMAND, [], cb) works because the pipeline looks up the fallback separately: after the commands and the intents from setPlatformParams, if none of them matched. A regular command with an empty slots array is silently not registered — addCommand('myCmd', [], cb) creates no triggers. The exception is welcome/help, for which the framework uses default slots when the list is empty.

    // The function that runs the next step in the middleware chain
    type MiddlewareNext = () => Promise<void>;

    // A middleware function
    type MiddlewareFn = (ctx: BotController, next: MiddlewareNext) => void | Promise<void>;
    // The parameters of a registered command
    interface ICommandParam<TBotController extends BotController = BotController> {
    slots?: TSlots; // Activation triggers (strings or RegExp)
    isPattern?: boolean; // Interpret slots as RegExp
    cb: (
    userCommand: string,
    botController: TBotController,
    ) => void | string | Promise<void | string>;
    regExp?: RegExp; // The compiled RegExp (filled automatically)
    isRegExpString: boolean; // The string RegExp flag
    }

    // The parameters of a step (a dialog chain)
    interface IStepParam<TBotController extends BotController = BotController> {
    stepName: string; // The unique step name
    // false — the step does not apply, the lookup continues with commands; a string is the response text
    cb: (botController: TBotController) => void | false | string | Promise<void | false | string>;
    }

    // The command slots type
    type TSlots = (string | RegExp)[];

    // A custom command resolver
    type TCommandResolver = (
    userCommand: string,
    commands: Map<string, ICommandParam>,
    ) => string | null | Promise<string | null>;

    Declarative handling of non-text updates — like bot.on(':photo') in Telegram frameworks, but for all connected platforms at once. The adapter determines the event type and writes it to controller.eventType; addEvent handlers are called before steps and commands.

    // Universal events (TEventType):
    type TEventType =
    | 'message' // a text message (the default)
    | 'photo'
    | 'voice'
    | 'video'
    | 'document'
    | 'location'
    | 'contact'
    | 'sticker'
    | 'callback' // an inline/callback button press
    | 'inline' // an inline query (Telegram only for now)
    | 'message_edited'
    | 'channel_post'
    | 'start' // the start of a dialog (the deep-link payload is in controller.payload)
    | 'subscribed'
    | 'unsubscribed'
    | 'auth' // account linking completed (Alice account_linking)
    | 'rating'; // a rating result (SmartApp)

    // Event layer validators (exported from 'umbot'):
    const ALL_EVENT_TYPES: readonly TEventType[]; // the list of all 17 universal events
    function isEventType(event: string): event is TEventType; // true if the event name is known to the framework

    Support is declared by the adapter itself (the supportedEvents field): Telegram — media/callback/inline/edited/channels; VK — message/callback; MAX — message/callback/start/edited; Viber — media types/start/subscribed/unsubscribed; Alice — message/auth; SmartApp — message/start/rating; Marusia — message/auth. The handler is simply not called where the event is physically impossible — a multi-platform bot does not break. A custom platform (BasePlatform) declares its own supportedEvents and takes part in the validation automatically: bot.addEvent warns if no connected adapter supports the event (the handler is still registered and starts working once the right platform is connected).

    A summary table of supportedEvents by adapter (the values come from supportedEvents in the adapters' code):

    Platform supportedEvents
    Telegram message, photo, voice, video, document, location, contact, sticker, callback, inline, message_edited, channel_post
    VK message, callback
    MAX message, callback, start, message_edited
    Viber message, photo, video, document, contact, location, sticker, start, subscribed, unsubscribed
    Alice message, auth
    Marusia message, auth
    SmartApp message, start, rating
    // A photo from the user — without parsing requestObject manually
    bot.addEvent('photo', (ctx) => {
    ctx.text = 'Great photo!';
    });

    // A button press (the payload is in ctx.payload)
    bot.addEvent('callback', (ctx) => {
    ctx.text = `You pressed: ${String(ctx.payload)}`;
    });

    // A "filter": intercept the message but hand it to the regular pipeline
    bot.addEvent('message', (ctx) => {
    if (ctx.userEvents?.auth?.status) return false;
    ctx.text = 'Intercepted!';
    });

    // Events can also be handled in action() by controller.eventType
    class MyController extends BotController {
    action(intentName: string | null): void {
    if (this.eventType === 'photo') this.text = 'A photo!';
    }
    }

    Handler semantics:

    • returned anything except false (including void) — the event is intercepted: a returned string becomes the response text; processing is finished, commands are not looked up;
    • returned false — "not my event", the pipeline continues (steps → commands → intents → fallback); this is the only way to pass the request on to the regular pipeline;
    • async handlers are supported, the framework awaits the result;
    • several handlers of one event run in registration order until the first non-false one.

    A callback button press handler by its payload — like bot.action() in competing frameworks. The button is created with a payload (buttons.addBtn('Buy', '', 'buy')), and the Telegram/VK/MAX adapters normalize the payload into a command name:

    bot.addCommand('catalog', ['catalog'], (_, ctx) => {
    ctx.text = 'Choose a product:';
    ctx.buttons.addBtn('iPhone', '', 'buy').addBtn('MacBook', '', 'buy');
    });

    // Fires on pressing a button with the 'buy' payload
    // (or the {"command":"buy"} payload): the button message goes as the 'buy' command
    bot.addAction('buy', (_, ctx) => {
    ctx.text = 'Placing your order...';
    });

    On platforms without callback buttons (Alice, Marusia) buttons send text that is matched by a regular slot — no handler is needed.

    For commands with RegExp slots, isPattern patterns and a matched regex group, the handler gets the ready match in controller.match — without running the regex again:

    bot.addCommand('order', [/(?:order|buy)\s+(\d+)/], (_, ctx) => {
    ctx.text = `Placing order No. ${ctx.match?.[1]}`;
    });

    match is computed lazily: the framework remembers the regex of the matched command and runs it only on the first read — requests that do not use groups spend no time on it. For string commands match === null.

    Unified access to the active platform's capabilities from a handler — without constructing Request classes manually:

    // A photo handler: send a photo in reply
    bot.addEvent('photo', async (ctx) => {
    await ctx.api?.sendPhoto('answer.jpg', { caption: 'Here is your report' });
    ctx.skipAutoReply = true; // the reply has already been sent manually
    });

    // Acknowledging a button press on any callback platform
    bot.addAction('buy', async (_, ctx) => {
    await ctx.api?.answerCallback('Order placed!');
    });

    The facade methods:

    Method Description Telegram VK MAX Viber
    sendPhoto Sends a photo (a local path, a URL or a file_id/attachment) ✓ ✓ (upload) ✓ —
    sendDocument Sends a file/document ✓ ✓ (upload) ✓ —
    sendAudio Sends audio ✓ — ✓ —
    sendVideo Sends video ✓ — ✓ —
    answerCallback A notification/snackbar in reply to a callback button press ✓ ✓ ✓ —
    can(method) Checks whether the current platform supports a method ✓ ✓ ✓ —

    The facade is a lazy object: it is created on the first access to ctx.api, and on voice platforms (Alice, SmartApp, Marusia) it is null (their reply is built as the webhook body — use card/sound). Unsupported methods log a warning and return null; support is checked in advance with can(). For Viber can() returns false for all methods — the Viber Bot API requires a URL and the file size, so the facade is not available there.

    The facade is chosen by the adapter: the createApi(controller) method of the IPlatformAdapter contract (the BasePlatform base implementation returns null). A custom platform connects its own facade by overriding this method — it returns an object implementing IControllerApi; an example is in platform-integration.md, the "The platform API" section.

    A multi-step form is a wrapper over steps: each field becomes a separate step, and the answers are collected into an object and passed to onComplete.

    // A single form field
    interface IAddFormField<TBotController extends BotController = BotController> {
    name: string; // The field key in the answers object
    prompt: string | ((ctx: TBotController) => string); // The question to the user
    // true — accepted; false — repeat the prompt; string — the error text for the user
    validate?: (value: string) => boolean | string | Promise<boolean | string>;
    }

    // addForm options
    interface IAddFormOptions<TBotController extends BotController = BotController> {
    fields: IAddFormField<TBotController>[]; // The fields, processed one after another
    onComplete: (ctx: TBotController, answers: Record<string, string>) => void | Promise<void>; // Called after all fields are filled
    cancelText?: string; // The text on cancel (by default 'Форма отменена.')
    cancelCommands?: string[]; // The cancel commands (by default ['отмена', 'cancel'])
    }
    bot.addForm('registration', {
    fields: [
    { name: 'name', prompt: 'What is your name?' },
    {
    name: 'email',
    prompt: 'Enter your email',
    validate: (v) => /\S+@\S+/.test(v) || 'Invalid email',
    },
    ],
    onComplete: (ctx, answers) => {
    ctx.text = `Thank you, ${answers.name}! We saved your email: ${answers.email}`;
    },
    });

    Intermediate answers are stored in userData.__formdata_<name>. After the form is filled or cancelled, the field is set to null rather than deleted: Alice removes a field from user_state_update only when its value is null, while a field deleted with delete would remain in the user state.

    removeForm('registration') removes the form and all its internal steps. The form step names have the __form_<name>_ prefix, so removeForm('user') also removes the user_2 form — use unique names.

    // Creating an interactive button
    getButton(
    appContext: AppContext,
    title: string | null,
    url: string | null,
    payload: TButtonPayload | null,
    options?: IButtonOptions
    ): IButtonType | null

    // Creating a link button
    getLinkButton(
    appContext: AppContext,
    title: string | null,
    url: string | null,
    payload: TButtonPayload | null,
    options?: IButtonOptions
    ): IButtonType | null
    // Creating an image for a card
    getImage(
    appContext: AppContext,
    image: string | null,
    title: string,
    desc = '',
    button: TButton | null = null,
    isToken = false
    ): IImageType | null

    Helpers for those who write their own platform adapter or their own API client. Usage details are in the adapter/platformAdapter.md document.

    // A unified format of a platform API request error message
    getErrorMsg(error: Error | string, path: string, url: string | null): string

    // A unified format of a missing platform token message
    getErrorToken(platform: string, methodName: string): string

    // Building the API facade of a built-in platform by controller.appType
    // (the core calls the adapter's createApi(); the dispatcher is kept for manual use)
    makePlatformApi(controller: BotController): TApiFacade | null

    The other adapter helpers are collected in the pUtils namespace (import { pUtils } from 'umbot/plugins'): working with media tokens (getImageToken, getSoundToken), parsing incoming requests (tryParse, normalizeActionPayload, getPlatformRequestData, setThisUserToNlu, telegramMessageEvent, viberMessageEvent) and building the response (getChatText, getSpeechText, getCorrectButtons, serializePlatformPayload).

    // The request processing result
    type TRunResult = object | string;
    import { BotController, WELCOME_INTENT_NAME } from 'umbot';

    class MyController extends BotController {
    public action(intentName: string | null): void {
    switch (intentName) {
    case WELCOME_INTENT_NAME:
    this.text = 'Hi! How can I help?';
    this.buttons.addBtn('Help').addBtn('About the app');
    break;

    case 'about':
    this.text = 'This is an example application built with umbot';
    this.card.setTitle('About the app').addImage('image_token');
    break;

    default:
    this.text = 'Sorry, I did not understand you';
    break;
    }
    }
    }
    import { Bot } from 'umbot';

    const bot = new Bot();

    // Adding a simple command
    bot.addCommand('greeting', ['hello', 'hi'], (_, bc) => 'Hi!');

    // Adding a command with a callback
    bot.addCommand(
    'numbers',
    ['\\b\\d{3}\\b'],
    (userCommand, botController) => {
    botController.text = `You entered a number: ${userCommand}`;
    },
    true,
    );
    interface GameData extends IUserData {
    score: number;
    level: number;
    example?: string;
    result?: number | string;
    isGame?: boolean;
    }

    class GameController extends BotController<GameData> {
    public action(intentName: string | null): void {
    // Initializing data on the first launch.
    // The data must be merged, not overwritten:
    // reassigning `this.userData = {...}` breaks saving to the database.
    if (!this.userData.score) {
    Object.assign(this.userData, {
    score: 0,
    level: 1,
    });
    }

    // Handling commands
    switch (intentName) {
    case 'addScore':
    this.userData.score += 10;
    this.text = `Your score: ${this.userData.score}`;
    break;
    }
    }
    }
    class ButtonController extends BotController {
    public action(intentName: string | null): void {
    switch (intentName) {
    case 'showButtons':
    // Adding buttons
    this.buttons.addBtn('Help').addBtn('Back').addBtn('Exit');
    this.text = 'Choose an action:';
    break;
    }
    }
    }
    class CardController extends BotController {
    public action(intentName: string | null): void {
    switch (intentName) {
    case 'showCard':
    // Creating a card
    this.card
    .setTitle('Card title')
    .addImage('image_token', ' ', 'Image description');
    this.text = 'Here is your card:';
    break;
    }
    }
    }
    class NluController extends BotController {
    public action(intentName: string | null): void {
    // Getting an intent from the NLU (for example, 'YANDEX.CONFIRM')
    const nluIntent = this.nlu.getIntent('YANDEX.CONFIRM');
    if (nluIntent) {
    // nluIntent is an INluIntent object with a slots property
    this.text = `Slots found: ${JSON.stringify(nluIntent.slots)}`;
    } else {
    this.text = 'Intent not found';
    }
    }
    }
    class AuthController extends BotController {
    public action(intentName: string | null): void {
    // Authorization check
    if (this.isAuth) {
    this.text = 'You are authorized';
    this.userToken = this.userToken || 'default_token';
    } else {
    this.text = 'Authorization is required';
    this.isAuth = true;
    }
    }
    }
    class RatingController extends BotController {
    public action(intentName: string | null): void {
    // Rating check
    if (this.isSendRating) {
    this.text = 'Thank you for your rating!';
    this.isSendRating = false;
    } else {
    this.text = 'Please rate our service';
    this.isSendRating = true;
    }
    }
    }

    The application context is the storage of the configuration, registries and connected modules. Each Bot instance has its own context (bot.getAppContext()), so several bots in one process do not share settings.

    Property Type Description
    appConfig Required<IAppConfig> The current configuration (with all defaults)
    platformParams IAppParam Platform parameters
    platforms Record<TAppType, IPlatformAdapter> The registry of connected platforms
    database { adapter?: IDatabaseAdapter, databaseInfo?: unknown, isSendConnect?: boolean } The connected DB adapter and connection information
    command CommandReg The command registry (the main access; handy getters below)
    commands Map<string, ICommandParam> All registered commands (a getter over command)
    steps Map<string, IStepParam> All registered steps (a getter over command)
    regexpGroup Map<string, IGroupData> Regex command groups (a getter over command)
    httpClient THttpClient The HTTP client (a public field, can be overridden)
    plugins TAppPlugin The plugin registry (the i18n, nlu, regExp slots + yours)
    Method Description
    log(...args) Logging
    logError(msg, meta?) Error logging
    logWarn(msg, meta?, options?) Warning logging. options.stderr: true — outside dev and without a custom logger, also write to stderr (for warnings that must not be missed)
    logMetric(name, value, label) Metric logging

    The component for page-by-page navigation through lists and menus.

    import { Navigation } from 'umbot';

    const nav = new Navigation(5); // 5 items per page
    const elements = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Getting the items of the current page
    const page = nav.getPageElements(elements);

    // Navigation by commands (Navigation recognizes the Russian words)
    nav.getPageElements(elements, 'дальше'); // the next page ("next")
    nav.getPageElements(elements, 'назад'); // the previous page ("back")

    // Finding an item: 'iPhone' must be on the CURRENT page (the window of the first
    // maxVisibleElements items) — the similarity search runs only inside it
    const item = nav.selectedElement(elements, 'iPhone', ['title']);
    Method Parameters Return value Description
    getPageElements elements?: T[] | null, text?: string T[] The items of the current page; without elements — the last passed list (mutates thisPage on the Russian "next"/"back")
    selectedElement elements: T[] | null, text: string, keys?: TKeys | null, thisPage?: number | null T | null Finds an item by value (by number or by text similarity) — only among the items of the current page. When skipping later parameters, pass them explicitly as null
    getPageNav isNumber?: boolean string[] Pagination button labels: ['Дальше 👉']/['👈 Назад', 'Дальше 👉'] or ['[1]', '2', '3'] — the "back" label is not returned on the first page, "next" on the last; with a single page — ['[1]']
    getPageInfo - string Information about the current page: "N страница из M" ("page N of M"; an empty string for a single page)
    getMaxPage elements?: T[] | null number The number of pages
    numberPage text: string boolean Recognizes a command like "2 страница" (a page number in Russian) and goes there
    Property Type Description
    thisPage number The current page number (0-indexed)
    maxVisibleElements number The maximum number of items per page

    Preloading media resources to platform servers.

    import { Preload } from 'umbot/preload';
    import { T_ALISA, T_TELEGRAM } from 'umbot/plugins';

    const preload = new Preload(bot.getAppContext());

    // Uploading images (Alice needs the skill's skill_id)
    await Promise.all(preload.loadImages(['./img.jpg'], [T_ALISA], { alisaSkillId: 'your-skill-id' }));

    // Uploading sounds
    await Promise.all(preload.loadSounds(['./sound.mp3'], [T_ALISA], { alisaSkillId: 'your-skill-id' }));

    // Telegram requires a recipient ID
    await Promise.all(preload.loadImages(['./img.jpg'], [T_TELEGRAM], { telegramUseId: 123 }));

    ⚠️ Uploading happens only for platforms that have a token set (appConfig.tokens or environment variables). Without configured tokens the methods return an empty array (no promises), and the upload silently does not happen. For unsupported platforms (Viber, SmartApp) promises do not get into the array at all.

    options of all four methods: alisaSkillId — the skill id (the Alice resource API is addressed by skill, and outside a request there is nowhere to take it from; without it Alice is skipped both when uploading and when deleting); telegramUseId — the user Telegram will send the file to in order to get its file_id.

    1. When loadImages() is called, the framework checks whether the database already has a token for this file (the ImageTokens model).
    2. If there is a token, the cached value is used (no upload happens).
    3. If there is no token, the file is uploaded to the platform server, and the received token is saved to the database.
    4. On the next access to the same file the token is taken from the database — with no upload delay.
    • Always, if your skill has images or sounds.
    • It is especially critical for voice platforms because of the practical limit of ~3 s (a warning after 2 s).
    Method Parameters Return value Description
    loadImages paths: string[], platforms?: TAppType[], options? Promise<string | null>[] Uploads images (resolves with the image token or null on error)
    loadSounds paths: string[], platforms?: TAppType[], options? Promise<string | null>[] Uploads sounds (resolves with the sound token or null on error)
    removeImages paths: string[], platforms?: TAppType[], options? Promise<boolean>[] Deletes images (implemented only for Alice and Marusia; for other platforms the promise resolves with true without deleting anything)
    removeSounds paths: string[], platforms?: TAppType[], options? Promise<boolean>[] Deletes sounds (implemented only for Alice and Marusia; for other platforms the promise resolves with true without deleting anything)

    The result of loadImages/loadSounds is the token of the uploaded media or null if the upload failed: check success with !== null.

    The custom logger interface. All methods are optional.

    interface ILogger {
    log?(...args: unknown[]): void;
    error?(message: string, meta?: Record<string, unknown>): void;
    warn?(message: string, meta?: Record<string, unknown>): void;
    metric?(name: string, value: unknown, labels?: Record<string, unknown>): void;
    maskSecrets?: boolean; // true by default: secret masking is always on, it is disabled only by an explicit maskSecrets: false
    }

    Interfaces for framework extensions.

    // A plugin class
    interface IPlugin {
    init: (appContext: AppContext, bot: Bot) => void;
    destroy: (bot: Bot) => void | Promise<void>;
    }

    // A plugin function (recommended)
    interface IPluginFn {
    (appContext: AppContext, bot: Bot): void | ((bot: Bot) => void);
    isPlugin: boolean; // REQUIRED: myPlugin.isPlugin = true;
    }

    Recommendation: instead of assigning myPlugin.isPlugin = true manually, use the createPlugin() helper — it sets the flag automatically, so you cannot forget it:

    import { createPlugin } from 'umbot';

    const myPlugin = createPlugin((appContext, bot) => {
    // initialization logic
    return () => {
    // cleanup logic on destruction
    };
    });
    bot.use(myPlugin);

    Constants of standard sounds and effects.

    Constant Description
    S_AUDIO_GAME_WIN A victory sound
    S_AUDIO_GAME_LOSS A loss sound
    S_AUDIO_GAME_8_BIT_COIN A coin
    S_AUDIO_NATURE_RAIN Rain
    S_AUDIO_NATURE_SEA The sea
    S_EFFECT_HAMSTER The hamster effect (a high voice)
    S_EFFECT_MEGAPHONE The megaphone effect

    The full list is in src/components/sound/constants.ts.

    A utility for working with strings.

    Method Parameters Return value Description
    Text.resize text: string | null, size?: number, isEllipsis?: boolean string Trims a string to a length
    Text.getText str?: string | string[] string Picks a random element from an array
    Text.isSayText find: string | RegExp | (string | RegExp)[], text: string, isPattern?: boolean, useDirectRegExp?: boolean, customReg?: RegExpConstructor boolean Checks whether a slot matches the text

    The framework collects execution time metrics of key operations. To enable them, implement the metric() method in the logger.

    Metric Constant What it measures
    Request time EMetric.REQUEST The time of an outgoing HTTP request to the platform API (url, method, status in labels)
    Webhook start EMetric.START_WEBHOOK The moment processing starts (the value is a timestamp, not a duration)
    Webhook time EMetric.END_WEBHOOK The total webhook processing time (the incoming request)
    Intent lookup EMetric.GET_INTENT The time to find a matching intent
    Command lookup EMetric.GET_COMMAND The time to find a matching command
    action execution EMetric.ACTION The execution time of the controller's action()
    Middleware EMetric.MIDDLEWARE The execution time of the middleware chain
    DB query (SELECT) EMetric.DB_SELECT The SELECT execution time
    DB query (INSERT) EMetric.DB_INSERT The INSERT execution time
    DB query (UPDATE) EMetric.DB_UPDATE The UPDATE execution time
    DB query (REMOVE) EMetric.DB_REMOVE The DELETE execution time

    A connection example:

    bot.setLogger({
    metric: (name: string, value: unknown, meta?: Record<string, unknown>) => {
    console.log(`[METRIC] ${name}: ${value}`, meta);
    },
    });

    An output example:

    [METRIC] umbot_get-command_duration_ms: 0.45 { commandName: 'weather', status: true }
    [METRIC] umbot_action_duration_ms: 12.3 { commandName: 'weather', platform: 'telegram', isCommand: true }
    

    The base class for working with data in the database. Extend it to create custom models (leaderboards, catalogs, etc.).

    import { Model, IModelState, IModelRules, AppContext } from 'umbot';

    interface IScoreState extends IModelState {
    userId: string | null;
    score: number | string | null; // string allows a text field label (attributeLabels)
    }

    const RULES: IModelRules[] = [
    { name: ['userId'], type: 'string', max: 250 },
    { name: ['score'], type: 'integer' },
    ];

    export class ScoreModel extends Model<IScoreState> {
    public static readonly TABLE_NAME = 'Scores';

    constructor(appContext: AppContext) {
    super(appContext);
    this.state = { userId: null, score: null };
    }

    rules() {
    return RULES;
    }
    attributeLabels() {
    return { userId: 'ID', score: 'Score' };
    }
    tableName() {
    return ScoreModel.TABLE_NAME;
    }
    }

    Usage in a controller:

    const score = new ScoreModel(this.appContext);
    score.state.userId = String(this.userId);

    if (await score.whereOne({ userId: score.state.userId })) {
    score.state.score = Number(score.state.score) + 1;
    await score.update();
    } else {
    score.state.score = 1;
    await score.add();
    }
    Method Description
    add() Inserts a new record
    update() Updates the current record
    remove() Deletes the record
    whereOne(where?) Finds one record by conditions
    where(where?, isOne?) Finds records by conditions
    query(callback) A raw database query
    save(isNew?) Saves (add if isNew=true, otherwise update)

    If the primary key is unique only together with another field (like userId in UsersData — within a platform), override the protected getUniqueKeys() method: the model adds these fields to the select/update/remove condition and passes them to the adapter in IQuery.uniqueKeys.

    protected getUniqueKeys(): string[] {
    return ['platform'];
    }

    The built-in model for storing userData. Usually it is not used directly — the framework works with it automatically through controller.userData.

    Built-in models for caching the tokens of uploaded media. They are managed by the framework automatically through Preload and the Card/Sound components.


    When you connect MongoAdapter or FileAdapter, umbot automatically creates the following tables (collections).

    Stores the state between requests for each user on each platform.

    Field Type Description
    userId string | number The user ID (the record key together with platform)
    data Record<string, unknown> The contents of ctx.userData — arbitrary JSON
    meta Record<string, unknown> Metadata: when it was created, the last request, the platform
    platform string The platform name ('telegram', 'alisa', ...)

    A record is identified by the userId + platform pair: Telegram user 42 and VK user 42 are different records (before 3.1.4 the lookup used only userId, and such users shared one record). FileAdapter stores rows under the <platform>:<userId> key and migrates rows of the old format on first access.

    You do not need to create tables manually. The description of all built-in tables (fields, keys, indexes) is exported as DB_TABLES_SCHEMA and passed to the DB adapter's ensureSchema() after connecting: FileAdapter creates the files itself, MongoAdapter creates indexes ({ userId, platform }, { platform, path }; MongoDB creates the collections on the first write), and an SQL adapter must create the tables (see the external adapter specification).

    A cache for images that need to be uploaded to the platform when they are sent.

    Field Type Description
    imageToken string The unique image ID on the platform (primary key)
    path string The local path or the CDN URL of the original
    platform string The name of the platform it was uploaded for

    Reusing the same path does not re-upload the image.

    The ImageTokens equivalent for audio files.

    Field Type Description
    soundToken string The unique sound ID on the platform (primary key)
    path string The local path or the CDN URL of the original
    platform string The platform name

    If you create your own model, extend Model:

    import { Model, IModelState, IModelRules, AppContext } from 'umbot';

    interface IMyState extends IModelState {
    id: string | null;
    name: string | null;
    age: number | string | null; // string allows a text field label (attributeLabels)
    }

    const RULES: IModelRules[] = [
    { name: ['name'], type: 'string', max: 200 },
    { name: ['age'], type: 'integer' },
    ];

    class MyTable extends Model<IMyState> {
    constructor(appContext: AppContext) {
    super(appContext);
    this.state = { id: null, name: null, age: null };
    }

    rules() {
    return RULES;
    }

    attributeLabels() {
    return { id: 'ID', name: 'Name', age: 'Age' };
    }

    tableName() {
    return 'my_table';
    }
    }

    Note: tableName(), rules() and attributeLabels() are public abstract methods; override them without the protected modifier. The allowed field types in rules() are 'text' | 'string' | 'integer' | 'int' | 'date' | 'bool'. The primary key is determined automatically by the 'id'/'ID' label in attributeLabels().

    Provider Notes
    FileAdapter A simple JSON file in ./json. Not thread-safe, for development/local tests only.
    MongoAdapter Production-ready. Uses the official mongodb v7 driver (Stable API v1) — compatible with current MongoDB Server versions.

    Tables, collections and indexes are created automatically: right after connecting to the database the framework calls the adapter's ensureSchema(), before the first query.


    Entry points and imports

    umbot provides a root export as well as separate import paths for specific tasks:

    // The main module — the core of the API
    import {
    Bot,
    BotController,
    BaseBotController,
    AppContext,
    WELCOME_INTENT_NAME,
    HELP_INTENT_NAME,
    FALLBACK_COMMAND,
    IUserData,
    IPlatformData,
    IUserEvent,
    TStatus,
    IAppConfig,
    IAppParam,
    IAppIntent,
    IAppDB,
    ITokenPlatform,
    ILogger,
    TAppType,
    TAppMode,
    EMetric,
    Buttons,
    Card,
    Sound,
    Nlu,
    Navigation,
    SoundConstants,
    IButton,
    IButtonType,
    IButtonOptions,
    TButton,
    IImageType,
    IImageParams,
    getImage,
    ISound,
    IEffect,
    INlu,
    INluFIO,
    INluGeo,
    INluDateTime,
    INluThisUser,
    INluIntents,
    INluResult,
    Model,
    UsersData,
    ImageTokens,
    SoundTokens,
    IModelRes,
    IQuery,
    IQueryData,
    IModelRules,
    IPlugin,
    IPluginFn,
    createPlugin,
    Text,
    getRegExp,
    isRegex,
    rand,
    keysCount,
    httpBuildQuery,
    isPromise,
    fread,
    fwrite,
    isFile,
    saveData,
    ICommandParam,
    IStepParam,
    TSlots,
    TCommandResolver,
    TBotControllerClass,
    MiddlewareFn,
    MiddlewareNext,
    } from 'umbot';

    // Platforms and DB adapters
    import {
    fullPlatforms,
    voicePlatforms,
    botPlatforms,
    adapters,
    AlisaAdapter,
    TelegramAdapter,
    VkAdapter,
    ViberAdapter,
    MaxAdapter,
    MarusiaAdapter,
    SmartAppAdapter,
    FileAdapter,
    MongoAdapter,
    BaseDbAdapter,
    BasePlatformAdapter,
    TContent,
    IAdapterOptions,
    T_ALISA,
    T_MARUSIA,
    T_SMART_APP,
    T_TELEGRAM,
    T_VK,
    T_VIBER,
    T_MAX_APP,
    AlisaConstants,
    MarusiaConstants,
    SmartAppConstants,
    YandexRequest,
    YandexImageRequest,
    YandexSoundRequest,
    YandexSpeechKit,
    TelegramRequest,
    VkRequest,
    ViberRequest,
    MaxRequest,
    MarusiaRequest,
    } from 'umbot/plugins';

    // Middleware
    import {
    rateLimiter,
    destroyRateLimiter,
    RateLimitQueueOverflowError,
    authGuard,
    requestId,
    maintenance,
    ipFilter,
    } from 'umbot/middleware';

    // Utilities (Text is also available from 'umbot')
    import { loadEnvFile } from 'umbot/utils';

    // Local testing
    import { BotTest, IBotTestParams } from 'umbot/test';

    // Media preloading
    import { Preload, IOptions as IPreloadOptions } from 'umbot/preload';

    // The simplified start utility
    import { run, IConfig, TMode } from 'umbot/build';
    Constant Value Purpose
    WELCOME_INTENT_NAME 'welcome' The greeting intent name (messageId === 0)
    HELP_INTENT_NAME 'help' The help intent name
    FALLBACK_COMMAND '*' The fallback command name
    T_ALISA 'alisa' The Alice platform identifier
    T_MARUSIA 'marusia' Marusia
    T_SMART_APP 'smart_app' Sber SmartApp
    T_TELEGRAM 'telegram' Telegram
    T_VK 'vk' VK
    T_VIBER 'viber' Viber
    T_MAX_APP 'max_app' MAX