# grammY: a TypeScript framework for Telegram bots, and where its Node-first assumptions show

> grammY is an MIT-licensed TypeScript framework for building Telegram bots on Node.js or Deno. The code is small, the documentation is the real product, and the ecosystem plugins are separate packages you install yourself.

**grammyjs/grammY** — The Telegram Bot Framework.

- Repository: https://github.com/grammyjs/grammY
- Website: https://grammy.dev
- Stars: 3,763 · Forks: 156
- Language: TypeScript
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/grammyjs-grammy

## What grammY solves, and who it is written for

Telegram exposes bots through an HTTP API, so a bot in any language is ultimately a loop that fetches updates and posts method calls. grammY is the TypeScript layer that sits between your handler code and that API. The README states that bots are written in TypeScript or JavaScript and run on Node.js or Deno, and that the package tracks Bot API 10.3 according to the badge in the repository README. The audience is narrow and clearly stated: developers who already know they are building a Telegram bot. The README points newcomers at Telegram's own Introduction for Developers before it shows any grammY code, which is an honest signal about where the framework's boundary sits. It does not teach you what a bot token is or how Telegram delivers updates. It assumes you will get a token from @BotFather and then hands you an object.

The design centre is the handler, not the transport. You register listeners against update types and the framework decides which ones fire. That is a smaller promise than a full bot platform, and it is the reason the core package has four runtime dependencies: @grammyjs/types, abort-controller, debug and node-fetch. Everything else in the ecosystem lives in separate packages published under the @grammyjs scope.

## How the update flow actually works

A grammY bot is a dispatcher over Telegram updates. You construct a Bot with your token, attach handlers with bot.on, and call bot.start, which the README describes as using long polling. In that mode the framework repeatedly asks Telegram for new updates and feeds each one through the registered listeners. The filter string is part of the mechanism: "message:text" matches messages that carry text, so a handler registered that way never sees a photo or a sticker.

Context objects are the second half of the design. The handler receives a ctx, and the README's example calls ctx.reply, which is a shortcut that sends a message back to the same chat the update came from. That shortcut is worth understanding before you scale up, because it hides the chat identifier. When you later need to reply somewhere else, or edit a message instead of sending one, you drop to the underlying API call and pass the chat id explicitly.

The repository layout backs this up. src/ holds the framework, test/ holds the test suite, and bundling/ exists to produce the browser build. The package.json exports map shows three entry points: the default, ./types, and ./web. The ./web entry resolves to out/web.mjs, and the README explains why: the core package also ships a web bundle so bots can run on Cloudflare Workers, imported as grammy/web. The Node path resolves to out/mod.js. Those artifacts are generated, not hand-written: the prepare script runs npm run backport, which invokes deno2node against tsconfig.json. The source is compiled for Node from a Deno-first codebase, and the README is explicit that all @grammyjs packages run natively on Deno while being compiled to still run on Node.js.

## Installing grammY and running a first echo bot

The README's quickstart is two commands and one file. Create a directory, install the package from npm, and write a bot that echoes text. The token comes from @BotFather; the README tells you to place it in the string passed to the Bot constructor.

```bash
npm install grammy
```

Then create bot.js. The README gives this example, which registers a single text handler and starts long polling.

```ts
const { Bot } = require("grammy");

// Create a bot object
const bot = new Bot(""); // <-- place your bot token in this string

// Register listeners to handle messages
bot.on("message:text", (ctx) => ctx.reply("Echo: " + ctx.message.text));

// Start the bot (using long polling)
bot.start();
```

Run it with node bot.js. The README states that the bot will then echo all received text messages, so the thing to check is a Telegram client: send the bot a text message and you should get the same text back with an Echo prefix. A non-text message produces nothing, because the filter only matches text.

If you are on Deno, the README says to import from https://deno.land/x/grammy/mod.ts instead. If you are targeting a browser-compatible runtime such as Cloudflare Workers, the README points at the web bundle with import { Bot } from "grammy/web". Those are three different entry points for the same framework, and picking the wrong one is the most common setup mistake.

## Where grammY is the wrong choice

The engine field in package.json declares node ^12.20.0 || >=14.13.1. That is an old floor, and it is a deliberate one, but it also means the framework is not written against modern Node APIs. If your project already requires a newer runtime for other reasons, that is fine. If you are choosing a runtime for a new bot, the constraint tells you the maintainers prioritise reach over novelty.

The larger limitation is scope. grammY's core is a dispatcher and an API client. Sessions, persistent storage, conversation flows and web framework integration are plugins in the wider ecosystem, which the README describes as a thriving ecosystem but does not enumerate in the repository. That means a bot which needs to remember state between updates is not a one-package install. You will be evaluating and versioning several @grammyjs packages, and each one has its own release cadence. The README does not document a compatibility matrix between the core and those plugins, so version skew is something you discover rather than something the repository warns you about.

Long polling is also the default the README shows, and it is the wrong transport for some deployments. Serverless and edge runtimes generally want webhooks, where Telegram pushes updates to an HTTPS endpoint you expose. The README mentions web framework integrations and the web bundle, but the quickstart never shows a webhook setup, so a reader who only follows the quickstart will build something that holds a process open. The README also does not document rollback behaviour or a downgrade path between releases.

## How grammY differs from telegraf

telegraf is the other widely used Telegram bot framework in the JavaScript ecosystem, and the difference is architectural rather than cosmetic. telegraf's core has historically carried more of the bot machinery itself, including session handling and stages for multi-step conversations, so a telegraf bot tends to be one dependency tree with a documented middleware ordering. grammY pushes those concerns out to plugins and keeps the core thin, which is why the published package has four runtime dependencies.

The practical consequence is where you look when something breaks. In a plugin-based framework, a session bug is a bug in the session plugin, versioned separately from the core, and the fix may land on a different schedule. In a bundled framework, it is the core's problem. Neither approach is free. grammY's split keeps the dispatcher small and the Bot API types in their own package, @grammyjs/types, at version 5.0.0, which means type updates can ship without touching the dispatcher. The cost is that you assemble the stack yourself and own the resulting version combination.

There is a second difference worth naming: the documentation. grammY's README points at grammy.dev and grammy.dev/ref as the primary resources and claims the documentation is the best in town. That is marketing, but the repository does support the claim structurally, since the docs are a separate site rather than a folder of markdown in this repository. If you evaluate frameworks by reading source, grammY gives you src/ and test/ and not much prose.

## Licence, releases and what an upgrade costs you

The package is MIT licensed, with the licence identifier in both package.json and the LICENSE file at the repository root. MIT is permissive: you can use grammY commercially, modify it and redistribute it, provided the copyright notice and licence text travel with it. That is a statement about the licence text, not legal advice, and if you are redistributing a modified build or bundling it into a product with its own licence obligations, that is a question for your own counsel.

The release cadence visible in the repository is steady. v1.45.0 shipped on 2026-07-16, v1.45.1 on 2026-07-17, and v1.46.0 on 2026-08-26. The repository's last push was 2026-08-26, and it is not archived. The version number in package.json matches the latest release tag, which means the published artifact and the tagged source are kept in step.

Upgrade cost depends on which entry point you use. Because the Node build is generated by deno2node rather than written by hand, the source you read on GitHub is not the file Node executes. A patch release that changes only the Node build is still a release you should read, but the diff in src/ may be small. The exports map is the other thing to watch: it declares separate paths for types, node, browser and default, so a change to which file a path resolves to can affect a Cloudflare Workers bot and a Node bot differently even when the framework logic is identical.

## Conclusion

Adopt grammY if you are writing a Telegram bot in TypeScript or JavaScript and you want a small core with the Bot API surface kept current: the README states the framework tracks Bot API 10.3, and the repository's last push was 2026-08-26. Do not adopt it if you want a framework that bundles sessions, storage and web server wiring in one install, because grammY keeps those as separate plugins. Before committing, check the Node engine range in package.json against your runtime, confirm which plugin packages you actually need, and open https://grammy.dev/ref to check that the Bot API methods you rely on are typed.

## FAQ

### How do I use grammY to build a Telegram bot?

Install the package with npm install grammy, create a Bot with the token you got from @BotFather, register handlers with bot.on, and call bot.start for long polling. The README's quickstart uses a single bot.on("message:text") handler that echoes the text back.

### Does grammY run on Deno as well as Node.js?

Yes. The README states that all @grammyjs packages run natively on Deno and are compiled to still run on Node.js, and that documentation is written Node.js-first. On Deno you import from https://deno.land/x/grammy/mod.ts.

### Which Node.js versions does grammY support?

The engines field in package.json declares node ^12.20.0 || >=14.13.1. That is the range the published package states, and it is the constraint to check before adopting it on a runtime you have already pinned.

### Can I run a grammY bot on Cloudflare Workers?

The README says the web bundle exists so bots can run on Cloudflare Workers, and that you import it with import { Bot } from "grammy/web". That resolves to the out/web.mjs entry in the package exports map.

## Sources

- [grammyjs/grammY on GitHub](https://github.com/grammyjs/grammY)
- [License: MIT](https://github.com/grammyjs/grammY/blob/main/LICENSE)
- [Project website](https://grammy.dev)
- [README](https://github.com/grammyjs/grammY/blob/main/README.md)
- [Releases](https://github.com/grammyjs/grammY/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/grammyjs-grammy
