Browser extension for downloading media from Telegram Web. It adds download buttons under photos, videos and files on web.telegram.org, and a popup for batch downloads and grabbing a whole channel.
Unofficial. Not affiliated with, endorsed by, or connected to Telegram FZ-LLC in any way.
Inline download buttons. A small download button shows up under every media message. Albums (several photos or videos in one message) get one button per item plus a "Download all" button for the group.
Batch download popup. Media you scroll past is collected into the popup. From there you can select some or all of it, download items one by one, zip everything into a single archive, or copy the direct links. The list keeps updating while the popup is open.
Whole-channel download (beta). Scrolls a chat's history and downloads every video, image and document it matches. You can cap how many of each type to take, and set an overall limit.
Byte-correct downloads. Files are rebuilt from range-chunked fetches with gap and size checks, instead of one plain fetch, so large videos come out complete and playable.
GIF and sticker filtering. Animated GIFs and stickers are skipped by default. Turn them on in Settings if you want them.
Light, dark and system themes, a configurable default save folder, and per-type filtering for channel downloads.
Not on the Chrome Web Store. Load it as an unpacked extension:
git clone https://github.com/psnwd/TGramSave.git
cd TGramSave
make install # or: bun install
make build # or: bun run buildThen in Chrome, Edge or Brave:
- Open
chrome://extensions - Turn on Developer mode (top right)
- Click Load unpacked and pick the
distfolder
After every rebuild, reload the extension from chrome://extensions and hard-refresh any open web.telegram.org tabs so they pick up the new content scripts.
make dev # Vite dev build, watches src/ and rebuilds into dist/
make typecheck # vue-tsc --noEmit only
make build # type-check, then production build into dist/
make package # build, then zip dist/ into tgramsave.zip
make icons # regenerate public/icons/{16,32,48,128}.png from a master.pngmake just wraps the matching bun run <script> commands (see the Makefile), so plain bun or npm works too.
Lint and format run through Biome (config in biome.json):
bun run check # Biome lint + format check, no writes. This is what CI runs
bun run check:fix # apply safe lint fixes and format
bun run format # format only
bun run ci # full local gate: biome ci, typecheck, buildCI is in .github/workflows/ci.yml. It runs biome ci, vue-tsc and a production build on every push to main and every pull request, then uploads the built dist/ as an artifact. Biome lints the TypeScript. The .vue files are checked by vue-tsc instead, since Biome's template analysis isn't reliable enough to lint them cleanly.
src/content-script/ runs in Telegram's isolated content-script world. It scans the DOM for media messages, injects the download buttons, and records what it finds in chrome.storage for the popup.
src/content-script-inject/ runs in the page's own MAIN world (declared with world: "MAIN" in the manifest). It needs same-origin access to Telegram's blob: and streaming URLs, which an isolated content script can't touch reliably. It does the byte-range fetching, reassembly and <a download> trigger.
src/channel-downloader/ is the whole-channel scroll-and-download feature.
src/background/ is the MV3 service worker. It passes messages between the content scripts and the popup, and keeps the toolbar badge count.
src/popup/ is the Vue 3 and Element Plus popup UI, with Download, Channel and Settings tabs.
| Permission | Why |
|---|---|
storage |
Recording found media, saving settings |
activeTab |
Talking to the current Telegram tab from the popup |
downloads, downloads.open |
Used by the channel downloader's bulk path |
host_permissions: web.telegram.org |
The extension only runs on Telegram Web |
No webRequest or declarativeNetRequest. Downloads work by fetching with the page's own session and cookies, not by intercepting or rewriting network traffic.
Issues and PRs welcome. Run bun run check and make typecheck before submitting.


