This page is machine-translated from the Russian original. If something reads oddly, the Russian version is the source of truth — open an issue.
umbot has no strict separation between "adapters" and "plugins" at the core level. Everything that extends the functionality implements the IPlugin interface (or IPluginFn) and is connected via bot.use(). DB and platform adapters are simply specialized plugins that extend base classes.
When bot.use(entity) is called, the framework checks the entity type. If it is an object (in particular, an instance of a plugin class), it calls entity.init(appContext, bot). The context (AppContext) is the hub that stores state, tokens, command registries and connected modules; each
Bot instance has its own.
appContext.plugins.appContext.plugins under any key you choose. You call them yourself from your code.appContext.database.adapter. How to write your own — see dbAdapter.md.appContext.platforms. How to write your own — see platformAdapter.md.A platform adapter is the "richest" kind of extension: besides parsing the request and building the response, it
declares which universal events it emits (supportedEvents → bot.addEvent), and it can
give the business logic an API facade of its platform (createApi → controller.api). Messengers have two more
extension points: a delivery ID for deduplicating webhook retries (getDeliveryId), a webhook signature flag
(isSignatureSupported) and long polling (getUpdates → bot.startPolling()); voice platforms have a response deadline
(getResponseTimeout). All of them are optional and described in platformAdapter.md.
You do not need to extend base classes. It is enough to implement a function or an object and put it into the right appContext.plugins slot.
import { Bot, AppContext, createPlugin } from 'umbot';
const myI18nPlugin = createPlugin((appContext: AppContext, bot: Bot) => {
// Register the plugin in the 'i18n' slot
appContext.plugins['i18n'] = (key: string, ...params: unknown[]) => {
return `Translation for: ${key}`;
};
// Return a cleanup function (optional)
return () => {
// Release resources when the plugin is destroyed
appContext.log('i18n plugin destroyed');
};
});
const bot = new Bot();
bot.use(myI18nPlugin);
createPlugin()sets theisPlugin = truemarker automatically. Without it,bot.use()treats the function as middleware rather than a plugin. If for some reason you do not use the helper, set the flag yourself:myI18nPlugin.isPlugin = true.
import { Bot, AppContext, IPlugin } from 'umbot';
class MyI18nPlugin implements IPlugin {
// The context is also needed in destroy, so it is saved during initialization
#appContext?: AppContext;
init(appContext: AppContext, bot: Bot): void {
this.#appContext = appContext;
appContext.plugins['i18n'] = (key: string, ...params: unknown[]) => {
return `Translation for: ${key}`;
};
}
destroy(bot: Bot): void {
// Release resources
this.#appContext?.log('i18n plugin destroyed');
}
}
const bot = new Bot();
bot.use(new MyI18nPlugin());
For system plugins to work correctly, follow their signatures:
| Plugin | Signature | Description |
|---|---|---|
| i18n | (key: string, ...params: unknown[]) => string | Text localization |
| nlu | (text: string, platformNlu: INlu, platform: string, request: unknown) => INlu | Natural language processing |
| regExp | () => RegExpConstructor | A custom RegExp implementation (for example, re2) |
Each slot accepts two equivalent forms: the function itself or an object with a getData method of the same
signature. The framework checks the type and calls either plugins.i18n(...) or
plugins.i18n.getData(...) — use the object form when the plugin needs its own state.
For i18n, the framework calls the slot with a single argument — the current controller.text as the key
(the additional signature parameters are reserved for the future). Unlike i18n, the NLU plugin
receives all 4 signature arguments (see the example below).
const i18nPlugin = createPlugin((appContext) => {
const translations: Record<string, string> = {
hello: 'Hello',
bye: 'Bye',
};
appContext.plugins['i18n'] = (key: string) => {
return translations[key] || key;
};
});
bot.use(i18nPlugin);
const nluPlugin = createPlugin((appContext) => {
appContext.plugins['nlu'] = (
text: string,
platformNlu: INlu,
platform: string,
request: unknown,
) => {
// Enrich the NLU with data from an external service
return {
...platformNlu,
intents: {
...platformNlu.intents,
custom_intent: { slots: [] },
},
};
};
});
bot.use(nluPlugin);
You can create a plugin for any task (caching, working with an external API, business rules) and register it under your own name.
// 1. Create and register the plugin
const myCustomCachePlugin = createPlugin((appContext: AppContext) => {
const cache = new Map<string, unknown>();
// Register it under your own unique key.
// The value must be either a function or an object with a getData method.
appContext.plugins['myCustomCache'] = {
getData(operation: unknown, key: unknown, value?: unknown): unknown {
if (operation === 'set') {
cache.set(String(key), value);
return true;
}
if (operation === 'get') {
return cache.get(String(key));
}
return undefined;
},
};
});
bot.use(myCustomCachePlugin);
// 2. Use it from your own code (for example, in a command or a controller)
bot.addCommand('save_data', ['save'], (text, controller) => {
// Access the plugin through appContext.
// The registry is typed with the generic AnyPluginData, so narrow the type to your implementation.
const cache = controller.appContext.plugins['myCustomCache'] as
{ getData: (...args: unknown[]) => unknown } | undefined;
if (cache) {
cache.getData('set', 'last_command', text);
controller.text = 'The data is saved in the custom cache!';
}
});
bot.use(plugin) calls plugin.init(appContext, bot).appContext.plugins.bot.clearUse() or shutting down the application calls plugin.destroy(bot).Important: the destroy method releases resources (closing connections, unsubscribing from events, clearing timers). If your plugin creates no resources, you can skip it.
Full reference — API v-3.1 · all versions.