You are building a plugin for Teleton, a Telegram AI agent on TON. Ask the user what plugin or tools they want to build, then follow this workflow.
Before building, read the relevant reference files from the teleton-plugins repo:
- Full rules & SDK reference:
CONTRIBUTING.md— complete guide with tool definition, SDK API tables, error handling, lifecycle, best practices, testing - Simple plugin example:
plugins/example/index.js— Pattern A (array of tools, no SDK) - SDK plugin example:
plugins/example-sdk/index.js— Pattern B (tools(sdk) with database, TON balance, Telegram messaging) - Advanced SDK plugin:
plugins/casino/index.js— real-world SDK plugin with TON payments, payment verification, isolated database, payout logic - Registry:
registry.json— list of all existing plugins (check for name conflicts) - README.md — project overview, plugin list, SDK section
Read at least CONTRIBUTING.md and the relevant example before building.
- Ask the user what they want (plugin name, what it does, which API or bot)
- Decide — determine if the plugin needs the SDK (see decision tree below)
- Plan — present a structured plan and ask for validation
- Build — create all files once the user approves
- Install — copy to
~/.teleton/plugins/and restart
Determine:
- Plugin name — short, lowercase folder name (e.g.
pic,deezer,weather) - Plugin type:
- Inline bot — wraps a Telegram inline bot (@pic, @vid, @gif, @DeezerMusicBot…)
- Public API — calls an external REST API, no auth
- Auth API — external API with Telegram WebApp auth
- Local logic — pure JavaScript, no external calls
- Tools — list of tool names, what each does, parameters
- Does it need GramJS? — yes for inline bots and WebApp auth
- Does it need the SDK? — use the decision tree below
The Plugin SDK (tools(sdk)) gives high-level access to TON, Telegram, database, logging, and config. Use it only when needed — simpler plugins should use Pattern A.
Use tools(sdk) (Pattern B) if ANY of these apply:
| Need | SDK namespace | Example |
|---|---|---|
| Check TON balance or wallet address | sdk.ton.getBalance(), sdk.ton.getAddress() |
Casino checking balance before payout |
| Send TON or verify payments | sdk.ton.sendTON(), sdk.ton.verifyPayment() |
Casino auto-payout, paid services |
| Get TON price or transactions | sdk.ton.getPrice(), sdk.ton.getTransactions() |
Portfolio tracker |
| Query jetton balances or metadata | sdk.ton.getJettonBalances(), sdk.ton.getJettonInfo() |
Token portfolio, DEX tools |
| Send jettons (TEP-74 transfers) | sdk.ton.sendJetton() |
Token payments, swaps |
| Query NFT items or metadata | sdk.ton.getNftItems(), sdk.ton.getNftInfo() |
NFT gallery, collection tools |
| Convert TON units or validate addresses | sdk.ton.toNano(), sdk.ton.fromNano(), sdk.ton.validateAddress() |
Any TON plugin |
| Send Telegram messages programmatically | sdk.telegram.sendMessage() |
Announcements, notifications |
| Edit messages or send reactions | sdk.telegram.editMessage(), sdk.telegram.sendReaction() |
Interactive UIs |
| Send dice/slot animations | sdk.telegram.sendDice() |
Casino dice game |
| Send media (photos, videos, files, stickers) | sdk.telegram.sendPhoto(), sdk.telegram.sendFile() |
Media bots, file sharing |
| Delete, forward, pin, or search messages | sdk.telegram.deleteMessage(), sdk.telegram.pinMessage() |
Moderation, archival |
| Schedule messages | sdk.telegram.scheduleMessage() |
Reminders, timed announcements |
| Create polls or quizzes | sdk.telegram.createPoll(), sdk.telegram.createQuiz() |
Engagement, trivia bots |
| Moderate users (ban/mute/unban) | sdk.telegram.banUser(), sdk.telegram.muteUser() |
Group moderation |
| Stars & gifts (balance, send, buy) | sdk.telegram.getStarsBalance(), sdk.telegram.sendGift() |
Gift trading, rewards |
| Post stories | sdk.telegram.sendStory() |
Promotional content |
| Lookup users, chats, or participants | sdk.telegram.getUserInfo(), sdk.telegram.getChatInfo() |
Analytics, admin tools |
| Need an isolated database | sdk.db (requires export function migrate(db)) |
Tracking user scores, history, state |
| Key-value storage with TTL | sdk.storage (auto-created, no migrate needed) |
Caching, rate limits, temp state |
| Manage API keys or secrets | sdk.secrets |
Plugins that call authenticated APIs |
| Plugin-specific config with defaults | sdk.pluginConfig + manifest.defaultConfig |
Customizable thresholds, modes |
| Structured logging | sdk.log.info(), sdk.log.error() |
Debug, monitoring |
Use tools = [...] (Pattern A) if ALL of these apply:
- Only calls external APIs (REST, GraphQL) — no TON blockchain interaction
- Does not need to send Telegram messages from code (only returns data to LLM)
- Does not need persistent state (no database)
- Does not need plugin-specific config
Examples:
| Plugin | Pattern | Why |
|---|---|---|
weather |
A (array) | Calls Open-Meteo API, returns data |
gaspump |
A (array) | Calls Gas111 API, uses WebApp auth |
pic |
B (SDK) | Uses sdk.telegram.getRawClient() for inline bot |
casino |
B (SDK) | Needs sdk.ton (payments), sdk.telegram (dice), sdk.db (history) |
example-sdk |
B (SDK) | Needs sdk.db (counters), sdk.ton (balance), sdk.telegram (messages) |
Note: Inline bots and WebApp auth plugins can use either context.bridge.getClient().getClient() (Pattern A with permissions: ["bridge"]) or sdk.telegram.getRawClient() (Pattern B, preferred).
Show this to the user and wait for approval:
Plugin: [name]
Pattern: [A (simple) | B (SDK)]
Reason: [why SDK is/isn't needed]
Tools:
| Tool | Description | Params |
|-------------|--------------------------|-------------------------------------|
| `tool_name` | What it does | `query` (string, required), `index` (int, optional) |
SDK features used: [none | sdk.ton, sdk.db, sdk.telegram, sdk.log, sdk.pluginConfig]
Files:
- plugins/[name]/index.js
- plugins/[name]/manifest.json
- plugins/[name]/README.md
- registry.json (update)
Do NOT build until the user says go.
Create all files in plugins/[name]/ following the patterns below.
ESM only — always export const tools, never module.exports.
Choose the right pattern:
For plugins that don't need TON, Telegram messaging, or persistent database.
Reference: plugins/example/index.js
const myTool = {
name: "tool_name",
description: "The LLM reads this to decide when to call the tool. Be specific.",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
index: { type: "integer", description: "Which result (0 = first)", minimum: 0, maximum: 49 },
},
required: ["query"],
},
execute: async (params, context) => {
try {
// logic here
return { success: true, data: { result: "..." } };
} catch (err) {
return { success: false, error: String(err.message || err).slice(0, 500) };
}
},
};
export const tools = [myTool];For plugins that need TON blockchain, Telegram messaging, isolated database, or config.
Reference: plugins/example-sdk/index.js (basic), plugins/casino/index.js (advanced)
export const manifest = {
name: "my-plugin",
version: "1.0.0",
sdkVersion: ">=1.0.0",
description: "What this plugin does",
defaultConfig: {
some_setting: "default_value",
},
};
// Optional: export migrate() to get sdk.db (isolated SQLite per plugin)
export function migrate(db) {
db.exec(`CREATE TABLE IF NOT EXISTS my_table (
id TEXT PRIMARY KEY,
value TEXT
)`);
}
export const tools = (sdk) => [
{
name: "my_tool",
description: "What this tool does",
parameters: { type: "object", properties: {}, },
scope: "always", // "always" | "dm-only" | "group-only" | "admin-only"
category: "data-bearing", // "data-bearing" (reads/queries) | "action" (writes/modifies)
execute: async (params, context) => {
try {
// SDK namespaces:
// sdk.ton — getAddress(), getBalance(), getPrice(), sendTON(), getTransactions(), verifyPayment()
// getJettonBalances(), getJettonInfo(), sendJetton(), getJettonWalletAddress()
// getNftItems(), getNftInfo(), toNano(), fromNano(), validateAddress()
// sdk.telegram — sendMessage(), editMessage(), deleteMessage(), forwardMessage(), pinMessage()
// sendDice(), sendReaction(), getMessages(), searchMessages(), getReplies()
// sendPhoto(), sendVideo(), sendVoice(), sendFile(), sendGif(), sendSticker()
// downloadMedia(), setTyping(), getChatInfo(), getUserInfo(), resolveUsername()
// getParticipants(), createPoll(), createQuiz(), banUser(), unbanUser(), muteUser()
// getStarsBalance(), sendGift(), getAvailableGifts(), getMyGifts(), getResaleGifts()
// buyResaleGift(), sendStory(), scheduleMessage(), getMe(), isAvailable(), getRawClient()
// sdk.db — better-sqlite3 instance (null if no migrate())
// sdk.storage — get(), set(key, val, {ttl?}), delete(), has(), clear() — auto-created KV, no migrate needed
// sdk.secrets — get(key), require(key), has(key) — resolves ENV → secrets file → pluginConfig
// sdk.log — info(), warn(), error(), debug()
// sdk.config — sanitized app config (no secrets)
// sdk.pluginConfig — plugin-specific config from config.yaml merged with defaultConfig
const balance = await sdk.ton.getBalance();
sdk.log.info(`Balance: ${balance?.balance}`);
return { success: true, data: { balance: balance?.balance } };
} catch (err) {
return { success: false, error: String(err.message || err).slice(0, 500) };
}
},
},
];
// Optional: runs after Telegram bridge connects
export async function start(ctx) {
// ctx.bridge, ctx.db, ctx.config, ctx.pluginConfig, ctx.log
}
// Optional: runs on shutdown
export async function stop() {
// cleanup
}SDK error handling:
- Read methods (
getBalance,getPrice,getTransactions,getMessages,getJettonBalances,getNftItems) returnnullor[]on failure — never throw - Write methods (
sendTON,sendJetton,sendMessage,sendDice,sendPhoto,banUser) throwPluginSDKErrorwith.code:WALLET_NOT_INITIALIZED— wallet not set upINVALID_ADDRESS— bad TON addressBRIDGE_NOT_CONNECTED— Telegram not readySECRET_NOT_FOUND—sdk.secrets.require()called for missing secretOPERATION_FAILED— generic failure
import { createRequire } from "node:module";
import { realpathSync } from "node:fs";
const _require = createRequire(realpathSync(process.argv[1]));
const { Api } = _require("telegram");Plugins can have their own npm deps. Add package.json + package-lock.json to the plugin folder — teleton auto-installs at startup via npm ci --ignore-scripts.
Use two createRequire instances to separate core and plugin-local deps:
import { createRequire } from "node:module";
import { realpathSync } from "node:fs";
// Core deps (from teleton runtime)
const _require = createRequire(realpathSync(process.argv[1]));
// Plugin-local deps (from plugin's node_modules/)
const _pluginRequire = createRequire(import.meta.url);
const { Address } = _require("@ton/core"); // core
const { getHttpEndpoint } = _pluginRequire("@orbs-network/ton-access"); // plugin-localSetup: cd plugins/your-plugin && npm init -y && npm install <deps>. Commit both package.json and package-lock.json. The node_modules/ folder is auto-created at startup.
With SDK plugins, prefer sdk.telegram.getRawClient() over context.bridge.getClient().getClient().
const API_BASE = "https://api.example.com";
async function apiFetch(path, params = {}) {
const url = new URL(path, API_BASE);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
url.searchParams.set(key, String(value));
}
}
const res = await fetch(url, { signal: AbortSignal.timeout(15000) });
if (!res.ok) {
throw new Error(`API error: ${res.status} ${await res.text().catch(() => "")}`);
}
return res.json();
}Preferred (SDK Pattern B) — define tools inside the (sdk) => [...] closure:
export const tools = (sdk) => [{
name: "my_inline_bot",
description: "...",
parameters: { ... },
execute: async (params, context) => {
const client = sdk.telegram.getRawClient();
// ... same GramJS API calls
},
}];Legacy (Pattern A) — uses context.bridge directly:
execute: async (params, context) => {
try {
const client = context.bridge.getClient().getClient();
const bot = await client.getEntity("BOT_USERNAME");
const peer = await client.getInputEntity(context.chatId);
const results = await client.invoke(
new Api.messages.GetInlineBotResults({
bot, peer, query: params.query, offset: "",
})
);
if (!results.results || results.results.length === 0) {
return { success: false, error: `No results found for "${params.query}"` };
}
const index = params.index ?? 0;
if (index >= results.results.length) {
return { success: false, error: `Only ${results.results.length} results, index ${index} out of range` };
}
const chosen = results.results[index];
await client.invoke(
new Api.messages.SendInlineBotResult({
peer,
queryId: results.queryId,
id: chosen.id,
randomId: BigInt(Math.floor(Math.random() * 2 ** 62)),
})
);
return {
success: true,
data: {
query: params.query,
sent_index: index,
total_results: results.results.length,
title: chosen.title || null,
description: chosen.description || null,
type: chosen.type || null,
},
};
} catch (err) {
return { success: false, error: String(err.message || err).slice(0, 500) };
}
}let cachedAuth = null;
let cachedAuthTime = 0;
const AUTH_TTL = 30 * 60 * 1000;
async function getAuth(bridge, botUsername, webAppUrl) {
if (cachedAuth && Date.now() - cachedAuthTime < AUTH_TTL) return cachedAuth;
const client = bridge.getClient().getClient();
const bot = await client.getEntity(botUsername);
const result = await client.invoke(
new Api.messages.RequestWebView({ peer: bot, bot, platform: "android", url: webAppUrl })
);
const fragment = new URL(result.url).hash.slice(1);
const initData = new URLSearchParams(fragment).get("tgWebAppData");
if (!initData) throw new Error("Failed to extract tgWebAppData");
cachedAuth = initData;
cachedAuthTime = Date.now();
return cachedAuth;
}Reference: plugins/casino/index.js
export function migrate(db) {
db.exec(`CREATE TABLE IF NOT EXISTS used_transactions (
tx_hash TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
amount REAL NOT NULL,
game_type TEXT NOT NULL,
used_at INTEGER NOT NULL DEFAULT (unixepoch())
)`);
}
export const tools = (sdk) => [{
name: "verify_and_process",
execute: async (params, context) => {
const payment = await sdk.ton.verifyPayment({
amount: params.amount,
memo: params.username,
gameType: "my_service",
maxAgeMinutes: 10,
});
if (!payment.verified) {
const address = sdk.ton.getAddress();
return { success: false, error: `Send ${params.amount} TON to ${address} with memo: ${params.username}` };
}
// Process the verified payment...
// payment.playerWallet — sender's address (for refunds/payouts)
// payment.compositeKey — unique tx identifier
// payment.amount — verified amount
return { success: true, data: { verified: true, from: payment.playerWallet } };
}
}];{
"id": "PLUGIN_ID",
"name": "Display Name",
"version": "1.0.0",
"description": "One-line description",
"author": { "name": "teleton", "url": "https://github.com/TONresistor" },
"license": "MIT",
"entry": "index.js",
"teleton": ">=1.0.0",
"tools": [
{ "name": "tool_name", "description": "Short description" }
],
"permissions": [],
"tags": ["tag1", "tag2"],
"repository": "https://github.com/TONresistor/teleton-plugins",
"funding": null
}Notes:
- Add
"sdkVersion": ">=1.0.0"only if usingtools(sdk)(Pattern B) - Add
"permissions": ["bridge"]only if usingcontext.bridgedirectly (not needed with SDK) permissionsis[]for most plugins- Add
"secrets"to declare required/optional secrets (see Secrets section below)
If your plugin needs API keys or other secrets, declare them in the manifest:
{
"secrets": {
"api_key": { "required": true, "description": "API key for service X" },
"webhook_url": { "required": false, "description": "Optional webhook endpoint" }
}
}Then access them via sdk.secrets.get("api_key") or sdk.secrets.require("api_key") in your tools.
# Plugin Name
One-line description.
| Tool | Description |
|------|-------------|
| `tool_name` | What it does |
## Install
mkdir -p ~/.teleton/plugins
cp -r plugins/PLUGIN_ID ~/.teleton/plugins/
## Usage examples
- "Natural language prompt the user would say"
- "Another example prompt"
## Tool schema
### tool_name
| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `query` | string | Yes | — | Search query |Add to the plugins array:
{
"id": "PLUGIN_ID",
"name": "Display Name",
"description": "One-line description",
"author": "teleton",
"tags": ["tag1", "tag2"],
"path": "plugins/PLUGIN_ID"
}- Copy:
cp -r plugins/PLUGIN_ID ~/.teleton/plugins/ - Commit:
git add plugins/PLUGIN_ID/ registry.json && git commit -m "PLUGIN_NAME: short description" - Ask user if they want to push.
- ESM only —
export const tools, nevermodule.exports - JS only — the plugin loader reads
.jsfiles only - Tool names —
snake_case, globally unique across all plugins, prefixed with plugin name - Defaults — use
??(nullish coalescing), never|| - Errors — always try/catch in execute, return
{ success: false, error }, slice to 500 chars - Timeouts —
AbortSignal.timeout(15000)on all external fetch calls - Per-plugin npm deps — plugins needing external packages add
package.json+package-lock.json; teleton auto-installs at startup. Use the dual-require pattern (see below) - GramJS — always
createRequire(realpathSync(process.argv[1])), neverimport from "telegram" - Client chain —
context.bridge.getClient().getClient()ORsdk.telegram.getRawClient()for raw GramJS - SDK preferred — when using SDK, prefer
sdk.telegramovercontext.bridge,sdk.dbovercontext.db - Scope — add
scope: "dm-only"on financial/private tools,scope: "group-only"on moderation tools,scope: "admin-only"for admin-only tools - Category — add
category: "data-bearing"for read/query tools,category: "action"for write/modify tools - Secrets — use
sdk.secretsinstead of hardcoded keys; declare inmanifest.secrets - Storage — prefer
sdk.storagefor caching and temp state oversdk.db(nomigrate()needed) - SDK decision — only use Pattern B if the plugin actually needs TON, Telegram messaging, database, secrets, storage, or config (see decision tree)
Available in execute(params, context) for all plugins (Pattern A and B):
| Field | Type | Description |
|---|---|---|
bridge |
TelegramBridge | Send messages, reactions, media (low-level) |
db |
Database | SQLite (shared — prefer sdk.db for isolation) |
chatId |
string | Current chat ID |
senderId |
number | Telegram user ID of caller |
isGroup |
boolean | true = group, false = DM |
config |
Config? | Agent config (may be undefined) |
Available only in tools(sdk) function plugins (Pattern B):
| Namespace | Methods |
|---|---|
sdk.ton |
Wallet: getAddress(), getBalance(addr?), getPrice(), sendTON(to, amount, comment?), getTransactions(addr, limit?), verifyPayment(params) |
Jettons: getJettonBalances(ownerAddr?), getJettonInfo(jettonAddr), sendJetton(jettonAddr, to, amount, opts?), getJettonWalletAddress(ownerAddr, jettonAddr) |
|
NFT: getNftItems(ownerAddr?), getNftInfo(nftAddr) |
|
Utilities: toNano(amount), fromNano(nano), validateAddress(addr) |
|
sdk.telegram |
Messages: sendMessage(chatId, text, opts?), editMessage(chatId, msgId, text, opts?), deleteMessage(chatId, msgId, revoke?), forwardMessage(fromChat, toChat, msgId), pinMessage(chatId, msgId, opts?), searchMessages(chatId, query, limit?), scheduleMessage(chatId, text, scheduleDate), getReplies(chatId, msgId, limit?), getMessages(chatId, limit?) |
Media: sendPhoto(chatId, photo, opts?), sendVideo(chatId, video, opts?), sendVoice(chatId, voice, opts?), sendFile(chatId, file, opts?), sendGif(chatId, gif, opts?), sendSticker(chatId, sticker), downloadMedia(chatId, msgId), setTyping(chatId) |
|
Social: getChatInfo(chatId), getUserInfo(userId), resolveUsername(username), getParticipants(chatId, limit?) |
|
Interactive: sendDice(chatId, emoticon, replyToId?), sendReaction(chatId, msgId, emoji), createPoll(chatId, question, answers, opts?), createQuiz(chatId, question, answers, correctIdx, explanation?) |
|
Moderation: banUser(chatId, userId), unbanUser(chatId, userId), muteUser(chatId, userId, untilDate?) |
|
Stars & Gifts: getStarsBalance(), sendGift(userId, giftId, opts?), getAvailableGifts(), getMyGifts(limit?), getResaleGifts(limit?), buyResaleGift(giftId) |
|
Stories: sendStory(mediaPath, opts?) |
|
Core: getMe(), isAvailable(), getRawClient() |
|
sdk.db |
better-sqlite3 instance (requires export function migrate(db)) |
sdk.storage |
get(key), set(key, value, opts?), delete(key), has(key), clear() — auto-created KV store with optional TTL, no migrate() needed |
sdk.secrets |
get(key), require(key), has(key) — resolves from: ENV var (PLUGINNAME_KEY) → secrets file → pluginConfig |
sdk.log |
info(), warn(), error(), debug() |
sdk.config |
Sanitized app config (no API keys) |
sdk.pluginConfig |
Plugin config from config.yaml merged with manifest.defaultConfig |
Resolves secrets from multiple sources in priority order: environment variable (PLUGINNAME_KEY) → secrets store (~/.teleton/plugins/data/name.secrets.json) → pluginConfig.
// Check if a secret is available
if (sdk.secrets.has("api_key")) {
const key = sdk.secrets.get("api_key"); // string | undefined
}
// Require a secret (throws PluginSDKError with code SECRET_NOT_FOUND if missing)
const apiKey = sdk.secrets.require("api_key");Declare required secrets in manifest.json so users know what to configure:
{
"secrets": {
"api_key": { "required": true, "description": "API key for service X" }
}
}Auto-created _kv table — no migrate() export needed. Returns null if no database is available.
// Set a value with optional TTL (milliseconds)
sdk.storage.set("rate_limit:user123", { count: 1 }, { ttl: 60000 }); // expires in 60s
// Get a value (returns undefined if missing or expired)
const data = sdk.storage.get("rate_limit:user123");
// Check existence (respects TTL)
if (sdk.storage.has("rate_limit:user123")) { /* ... */ }
// Delete a key (returns true if it existed)
sdk.storage.delete("rate_limit:user123");
// Clear all keys for this plugin
sdk.storage.clear();// Get all jetton balances for the agent wallet (or a specific address)
const balances = await sdk.ton.getJettonBalances(); // JettonBalance[]
const otherBalances = await sdk.ton.getJettonBalances("UQ...");
// Get jetton metadata
const info = await sdk.ton.getJettonInfo("EQ...jetton_master"); // JettonInfo | null
// Send jettons
const result = await sdk.ton.sendJetton(
"EQ...jetton_master", // jetton master address
"UQ...recipient", // destination
"1000000000", // amount in smallest units
{ comment: "payment" } // optional
); // JettonSendResult
// Get jetton wallet address
const wallet = await sdk.ton.getJettonWalletAddress("UQ...owner", "EQ...jetton"); // string | null// Get all NFTs for the agent wallet (or a specific address)
const nfts = await sdk.ton.getNftItems(); // NftItem[]
// Get NFT metadata
const nft = await sdk.ton.getNftInfo("EQ...nft_address"); // NftItem | nullconst nano = sdk.ton.toNano(1.5); // bigint — 1500000000n
const tons = sdk.ton.fromNano(1500000000n); // string — "1.5"
const valid = sdk.ton.validateAddress("UQ..."); // boolean// photo/video/voice/file/gif can be a file path (string) or Buffer
await sdk.telegram.sendPhoto(chatId, "/path/to/image.jpg", { caption: "Look!" });
await sdk.telegram.sendVideo(chatId, videoBuffer, { caption: "Video" });
await sdk.telegram.sendVoice(chatId, voiceBuffer, { caption: "Listen" });
await sdk.telegram.sendFile(chatId, "/path/to/doc.pdf", { caption: "Document" });
await sdk.telegram.sendGif(chatId, gifBuffer);
await sdk.telegram.sendSticker(chatId, stickerBuffer);
// Download media from a message
const buffer = await sdk.telegram.downloadMedia(chatId, messageId); // Buffer | null
// Show typing indicator
await sdk.telegram.setTyping(chatId);await sdk.telegram.deleteMessage(chatId, messageId, true); // revoke=true deletes for everyone
await sdk.telegram.forwardMessage(fromChatId, toChatId, messageId);
await sdk.telegram.pinMessage(chatId, messageId, { silent: true });
const results = await sdk.telegram.searchMessages(chatId, "query", 20);
const replies = await sdk.telegram.getReplies(chatId, messageId, 10);
await sdk.telegram.scheduleMessage(chatId, "Reminder!", new Date("2026-03-01T10:00:00Z"));const chat = await sdk.telegram.getChatInfo(chatId); // ChatInfo | null
const user = await sdk.telegram.getUserInfo(userId); // UserInfo | null
const peer = await sdk.telegram.resolveUsername("username"); // ResolvedPeer | null
const members = await sdk.telegram.getParticipants(chatId, 100); // UserInfo[]await sdk.telegram.createPoll(chatId, "Favorite color?", ["Red", "Blue", "Green"], { anonymous: false });
await sdk.telegram.createQuiz(chatId, "2+2=?", ["3", "4", "5"], 1, "Basic math!");await sdk.telegram.banUser(chatId, userId);
await sdk.telegram.unbanUser(chatId, userId);
await sdk.telegram.muteUser(chatId, userId, Math.floor(Date.now() / 1000) + 3600); // mute 1hconst balance = await sdk.telegram.getStarsBalance(); // number
await sdk.telegram.sendGift(userId, giftId, { message: "Enjoy!" });
const gifts = await sdk.telegram.getAvailableGifts(); // StarGift[]
const mine = await sdk.telegram.getMyGifts(10); // ReceivedGift[]
const resale = await sdk.telegram.getResaleGifts(10); // StarGift[]
await sdk.telegram.buyResaleGift(giftId);await sdk.telegram.sendStory("/path/to/media.jpg", { caption: "Check this out!" });const client = sdk.telegram.getRawClient(); // raw GramJS TelegramClient | null
// Use for inline bots, WebApp auth, custom MTProto calls| Type | Key fields |
|---|---|
JettonBalance |
jettonAddress, balance, metadata (name, symbol, decimals, image) |
JettonInfo |
address, totalSupply, metadata (name, symbol, decimals, image, description) |
JettonSendResult |
hash, lt, success |
NftItem |
address, collectionAddress, metadata (name, description, image), ownerAddress |
ChatInfo |
id, title, type, participantsCount, username |
UserInfo |
id, firstName, lastName, username, phone, bot |
ResolvedPeer |
id, type, username |
MediaSendOptions |
caption?, replyToId?, silent? |
PollOptions |
anonymous?, multipleChoice?, quizMode? |
StarGift |
id, name, price, availability, image |
ReceivedGift |
id, fromUser, gift, date, message |
SecretDeclaration |
required (boolean), description (string) |
StorageSDK |
get(), set(), delete(), has(), clear() |
SecretsSDK |
get(), require(), has() |
ToolCategory |
"data-bearing" | "action" |
StartContext |
bridge, db, config, pluginConfig, log |
Only needed for Pattern A plugins that use context.bridge directly:
await context.bridge.sendMessage({ chatId, text, replyToId?, inlineKeyboard? });
await context.bridge.sendReaction(chatId, messageId, emoji);
await context.bridge.editMessage({ chatId, messageId, text, inlineKeyboard? });
await context.bridge.setTyping(chatId);
const msgs = await context.bridge.getMessages(chatId, limit);
const peer = context.bridge.getPeer(chatId);
const gramjs = context.bridge.getClient().getClient();