telebot exposes a stable extension surface in both languages. A plugin bundles bot behavior — middleware, handlers, API hooks, lifecycle — behind a single named object (or package, in Go) that installs cleanly, coexists with other plugins, and can be removed.
| Layer | What you can do | Node API | Go API |
|---|---|---|---|
| Update pipeline | Wrap every update; add handlers / error handlers | app.use(fn), app.addHandler() |
router.Use(mw), router.Command/Text/Handle |
| Context & state | Read/write per-user/chat state; attach per-update sessions | context.user_data / chat_data |
c.Set(key, v) / c.Get(key) |
| API traffic | Mutate outgoing payloads, observe/replace results, react to failures |
bot.transformRequest(fn, tag?),
bot.transformResponse(fn, tag?),
bot.onApiError(fn, tag?)
|
b.OnResponse(h), b.OnError(h) (return unregister) |
| Lifecycle | Async setup/teardown around start & stop | app.onInit(hook), app.onShutdown(hook) |
Your own init before RunPolling / context cancellation |
| Ordering & removal | Declare dependencies, control order, detach cleanly |
dependsOn, priority,
app.removePlugin(name)
|
router.UseNamed(name, mw) / router.RemoveMiddleware(name) |
telebot-tsA plugin is a named object with a synchronous install():
import type { Plugin, Application } from "telebot-ts";
export function protectContent(): Plugin {
return {
name: "telebot-plugin-protect-content",
// Install only after these plugins; lower priority installs first
// among simultaneously ready plugins.
dependsOn: [],
priority: 0,
install(app: Application) {
// 1. Wrap every dispatched update.
app.use(async (context, next) => {
const start = performance.now();
await next();
console.log(`update ${context.update.update_id} in ${performance.now() - start}ms`);
});
// 2. Decorate every outgoing API payload. The tag lets
// app.removePlugin() detach these hooks later.
app.bot.transformRequest((method, payload) => {
if (method === "sendMessage") payload.protect_content = true;
}, "telebot-plugin-protect-content");
// 3. Async setup runs before polling/webhook starts.
app.onInit(async () => { /* open connections */ });
app.onShutdown(() => { /* flush and close */ });
},
uninstall(app: Application) {
// Runs before removePlugin() deregisters everything above.
},
};
}
app.usePlugin(protectContent()); // duplicate names throw
app.flushPlugins(); // usually automatic
app.hasPlugin("telebot-plugin-protect-content");
app.pluginState("telebot-plugin-protect-content").calls = 0; // namespaced state
app.removePlugin("telebot-plugin-protect-content"); // full detach
dependsOn dependencies are not installed yet are deferred and
installed automatically once the dependency arrives. Dependencies still missing when
runPolling/runWebhook starts make the app throw at startup.
telebot-goA Go plugin is a package exporting a constructor that
returns a routing.MiddlewareFunc. The built-in
pkg/plugins/i18n package is the reference implementation:
import (
"github.com/Nam088/telebot/packages/go/pkg/plugins/i18n"
"github.com/Nam088/telebot/packages/go/pkg/routing"
)
// Register under a name so it can be removed later.
_ = router.UseNamed("i18n", i18n.New(i18n.Options{
DefaultLocale: "en",
Locales: map[string]map[string]string{
"en": {"hello": "Hello, {name}!"},
"vi": {"hello": "Xin chào, {name}!"},
},
}))
router.Text("", func(c *routing.Context) error {
session, _ := i18n.For(c) // per-update session via the context store
_, err := c.Reply(session.T("hello", map[string]string{"name": "Nam"}))
return err
})
// Detach later:
router.RemoveMiddleware("i18n")
To observe API traffic, register hooks on the *bot.Bot — both return an
unregister closure:
off := b.OnResponse(func(method string, result json.RawMessage) {
log.Printf("%s ok (%d bytes)", method, len(result))
})
offErr := b.OnError(func(method string, err error) {
log.Printf("%s failed: %v", method, err)
})
defer off()
defer offErr()
Both frameworks ship an internationalization plugin demonstrating the full surface:
middleware attaches a per-update translation session, locale is resolved from stored user
preference → Telegram language_code → default, and
{placeholder} interpolation is built in.
| Node | Go | |
|---|---|---|
| Install | app.usePlugin(plugins.i18n(options)) |
router.UseNamed("i18n", i18n.New(options)) |
| Access in handler | i18nFor(context).t("hello", { name }) |
i18n.For(c) → session.T("hello", params) |
| Persist locale | session.setLocale("vi") (stored in user_data) |
session.SetLocale("vi") (current update) |
telebot-plugin-* recommended).install() synchronous; defer async work to
onInit hooks. Tag all bot hooks with your plugin name so
removePlugin can detach them.
UseNamed so it is removable.
fetch in Node,
httptest server in Go); never hit the real Telegram API in unit tests.