Migrating from GramJS
teleproto is its own library, ideologically inspired by GramJS and going its own way from there. The public surface still resembles GramJS closely enough that most migrations are a single import swap — but a handful of additions and one rename are worth knowing about before you ship.
Why fork#
GramJS shipped the foundation. teleproto picks up where it slowed down: ongoing maintenance against newer TL layers, plus the features Telegram added to the auth and abuse flows after the original codebase stopped tracking them — email verification on first sign-in, reCaptcha challenges on suspicious requests, theFrozen* error family for accounts and methods under restriction.
The public surface stays GramJS-compatible by design. If your GramJS code compiles and runs today, the teleproto port is almost always a find-and-replace on the import path.
// before
import { TelegramClient } from "telegram";
import { StringSession } from "telegram/sessions";
import { NewMessage } from "telegram/events";
// after
import { TelegramClient } from "teleproto";
import { StringSession } from "teleproto/sessions";
import { NewMessage } from "teleproto/events";Renamed / removed#
One rename to flag. sendReadAcknowledge is gone; the replacement is markAsRead with a different signature. The new shape takes the message id (or undefined for "everything in this chat") as a positional argument, and clearMentions moves into an options object.
// GramJS
await client.sendReadAcknowledge(chat, { maxId: 123 });
// teleproto — single message form
await client.markAsRead(chat, 123);
// teleproto — mark the whole chat read + clear mentions
await client.markAsRead(chat, undefined, { clearMentions: true });Nothing else from the GramJS surface has been removed outright. If a method went missing on your end after the swap, it's almost certainly a TL-layer signature change rather than a rename — see the callout at the bottom of the page.
New TelegramClient params#
TelegramClientParamspicks up four fields that weren't in upstream GramJS:
maxConcurrentDownloads— global cap on simultaneous file downloads.downloadPool—Partial<FilePoolOptions>for tuning worker count and retry behaviour per file.reCaptchaCallback—(siteKey: string) => Promise<string>. Called when Telegram demands a captcha mid-request; return the solved token.testServers— boolean. Connects to Telegram's test data centres instead of production. Handy for CI accounts.
const client = new TelegramClient(session, apiId, apiHash, {
connectionRetries: 5,
// cap parallel downloads across the whole client
maxConcurrentDownloads: 4,
// per-file worker pool, retry policy, etc.
downloadPool: { workers: 4 },
// called when Telegram challenges a request with a reCaptcha
reCaptchaCallback: async (siteKey) => {
return await solveCaptcha(siteKey); // your provider
},
// route through Telegram's test DCs instead of production
testServers: false,
});New auth params#
UserAuthParams grows three fields to cover the challenges Telegram now puts in front of first-time sign-ins:
emailAddress: () => Promise<string>— supplies the email when Telegram asks for one during registration.emailVerification: () => Promise<{ code: string } | { token: string }>— returns either the 6-digit code or the OAuth-style token depending on which channel Telegram used.reCaptchaCallback: (siteKey: string) => Promise<string>— same signature as the client-level one, but supplied per-call.
await client.start({
phoneNumber: () => prompt("Phone: "),
phoneCode: () => prompt("SMS code: "),
password: () => prompt("2FA: "),
// Telegram now sometimes requires an email on first sign-in
emailAddress: () => prompt("Email: "),
emailVerification: async () => ({ code: await prompt("Email code: ") }),
// and a captcha on suspicious flows
reCaptchaCallback: async (siteKey) => solveCaptcha(siteKey),
onError: console.error,
});All three are optional. If Telegram doesn't challenge the flow, they never fire.
New error classes#
Five new error types ship from teleproto/errors. Catch them if you already have a try/catch around client.invoke calls:
FrozenError— base class. The account is restricted; most write operations will reject until the user resolves it in-app.FrozenMethodError— specific method is frozen for this account (e.g. sending to non-contacts). The rest of the API still works.FrozenParticipantError— the target participant is frozen; you can't add, remove, or message them.EmailUnconfirmedError— the account email hasn't been verified yet. Surfaces during sign-in when the user skipped email verification.SlowModeWaitError— chat has slow mode on; carries.secondslikeFloodWaitError. Wait, then retry.
Session strings#
You don't need to re-authenticate users when you switch. teleproto's StringSessionreads the legacy 352-character GramJS / Telethon format directly — pass it to the constructor and it loads as IPv4. New saves come out in teleproto's version-prefixed format (a leading 1 followed by base64), which both versions of the library can read going forward.
import { StringSession } from "teleproto/sessions";
// Your existing 352-char GramJS/Telethon string still works.
const session = new StringSession(legacyTelethonString);
// New saves come out in the version-prefixed format
// ("1" + base64). Both formats are read on load.
const fresh = session.save();Migration steps#
- Swap every
from "telegram"import forfrom "teleproto"(and the same on the/sessions,/events,/errors,/tlsubpaths). Uninstalltelegram, installteleproto. - Re-run
tsc --noEmit. Newer TL layers tighten some request and response types — fix what the compiler flags before you run the code. - If you catch errors from
teleproto/errors, add cases forFrozenError,FrozenMethodError,FrozenParticipantError,EmailUnconfirmedError, andSlowModeWaitErrorwhere they make sense. - Search for
sendReadAcknowledgeand rewrite each callsite tomarkAsReadwith the new signature.