npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-telegram-mini-app

v0.3.1

Published

Telegram Mini App dev tooling for Vite: real signed initData in a plain browser, a WebApp mock and a debug panel that doubles as an inspector inside Telegram.

Readme

vite-plugin-telegram-mini-app

npm license CI

Run a Telegram Mini App in a plain browser during development, with a debug panel that also works inside Telegram.

The dev server signs a real initData with your bot token and installs a window.Telegram.WebApp facade. The signature is genuine, so the backend accepts it with no dev flag and no skipped validation. Inside a real Telegram webview the plugin installs nothing and the panel becomes a read-only inspector.

Install

npm i -D vite-plugin-telegram-mini-app
// vite.config.ts
import { defineConfig } from 'vite'
import { tma } from 'vite-plugin-telegram-mini-app'

export default defineConfig({
  plugins: [
    tma({
      users: [
        { id: 1001, first_name: 'Alice', last_name: 'Adams', username: 'example_telegram_alice' },
        { id: 1002, first_name: 'Bob', username: 'example_telegram_bob' },
      ],
    }),
  ],
})

The token is read from TELEGRAM_BOT_TOKEN in the dev machine's environment. Signing happens in Node; the token never reaches the browser.

A draggable button appears in the corner. Clicking it opens the panel, which has three tabs:

  • Identity switches between the users you passed, an unregistered guest with a random id, and anonymous. Anonymous means an empty initData, which is what Telegram itself sends for keyboard-button and inline-mode launches.
  • Environment holds browser mode, theme, a collapsed viewport, a simulated keyboard, start_param, an expired signature, platform and version.
  • Data shows the signed initData parsed by field, a live snapshot of window.Telegram.WebApp, and a copy button.

Theme, collapsed viewport and keyboard apply immediately and fire themeChanged / viewportChanged. The rest changes the signature, so the page reloads. Selections persist across reloads until reset, except the two viewport toggles, which are client state and start over.

Three of those states deserve a note. Browser mode installs no mock at all, so the app takes its "not in Telegram" path: isTMA() is false and retrieveLaunchParams() throws. Expired signature keeps the hash valid but dates auth_date a day back, so a correct backend rejects it. Collapsed is how a mini app opens on a phone: isExpanded is false and the viewport is about half the window until expand() is called. The mock starts expanded, like the desktop client, and WebApp.expand() or web_app_expand really expands it.

Deep link is off unless you switch it on or set the option, because an app opened from a chat carries no start_param at all. Switched on, the value is signed into initData, so the app reads it from initDataUnsafe.start_param and the SDK from retrieveLaunchParams().tgWebAppStartParam, exactly as with a real link.

The roster is fetched when the panel opens rather than inlined into every page, since users may query a database. Search runs on the server and returns the first 20 matches.

Inspector in production

The inspector reads the environment and fakes nothing, so it also works in a built app. It is a separate entry point that your code mounts itself:

const { mountInspector } = await import('vite-plugin-telegram-mini-app/inspector')
mountInspector({ eruda: () => import('eruda') })

It shows initData, launch params, theme, viewport and safe area, and can open an eruda console. On a phone that is usually the only way to see any of it.

The plugin performs no access checks: gate the import by role, a stored flag or anything else you prefer. Note that the panel shows the user their own initData and offers to copy it.

Mock code cannot reach this entry point: the switcher, the WebApp facade and the SDK glue are not imported from it, and a test walks the built import graph to keep it that way.

Options

| Option | Default | Meaning | | ----------- | -------------------------------- | ------------------------------------------------------ | | botToken | process.env.TELEGRAM_BOT_TOKEN | Signing key. Without it initData is empty | | users | [] | Array or function (async allowed) returning the roster | | mode | 'auto' | webapp, sdk, or auto by detected packages | | sdkModule | detected | Which package to take mockTelegramEnv from | | theme | 'auto' | light, dark, or follow prefers-color-scheme | | platform | 'tdesktop' | WebApp.platform | | version | '7.0' | WebApp.version | | startParam| '' | Deep link payload, as from a ?startapp= link | | panel | true | Show the panel | | eruda | true | Show the console button; eruda loads on click | | stateFile | Vite cache | Where the selected identity is stored |

users runs in Node, so the roster can come from your database:

tma({ users: async () => (await import('./server/src/db/user.js')).listUsers() })

SDK mode

Apps built on @tma.js/sdk or @telegram-apps/sdk never read window.Telegram.WebApp. For them the plugin calls mockTelegramEnv from the package the app itself has installed and passes the signed initData into it. Buttons still render: the plugin intercepts web_app_setup_main_button and web_app_setup_back_button and sends clicks back as events, and answers theme, viewport and safe-area requests locally.

Both package generations are supported; they differ in the shape of the onEvent callback and the plugin accepts either. If both are installed, set sdkModule explicitly.

Frameworks without an index.html

Apps with an index.html get the script through transformIndexHtml. Frameworks that render the document themselves never call that hook, so the plugin also intercepts the HTML response. This is what makes react-router in framework mode work.

Limitations

  • The signature field is signed with Telegram's ed25519 key and cannot be produced here. A backend that verifies it instead of the HMAC hash will reject the mock.
  • Every served HTML gets the signature inlined, including responses going out through a public tunnel. Skipping injection for non-local hosts is not implemented yet.
  • Signing is local. There is no service to hand out access without handing out the bot token.

Development

npm run build
npm test
npm run playground:webapp     # raw window.Telegram.WebApp
npm run playground:sdk        # @telegram-apps/sdk
npm run playground:tmajs      # @tma.js/sdk
npm run playground:rr         # react-router 8, framework mode
npm run playground:inspector  # no plugin: the app mounts the inspector itself

Playgrounds are npm workspaces and consume the plugin by name, so they exercise the published exports map.

License

MIT.

The panel icon is from Solar by 480 Design, used under CC BY 4.0.