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-whereami

v0.7.0

Published

Tints your favicon and prefixes the page title per environment (dev/staging/prod), plus an optional build-info banner in <head> and the browser console.

Readme

vite-plugin-whereami

Tints your favicon and prefixes the page title per environment, so you never mistake a staging tab for prod again — plus an optional build-info banner (name/version/environment) in <head> and the browser console, and an on-screen corner badge for build info that's always visible, custom metadata included.

Inspired by this tip about using a different favicon per environment to keep tabs visually distinct.

Three browser tabs and console banners showing an untouched prod favicon/title, an orange-tinted staging one, and a green-tinted dev one

Install

bun add -D vite-plugin-whereami

Or, let an AI coding agent do it

Paste this into Claude Code, OpenCode, Cursor, Copilot, or whatever agent you're using:

Install and configure vite-plugin-whereami (https://www.npmjs.com/package/vite-plugin-whereami)
in this project:

1. Add it as a dev dependency with this project's package manager.
2. Wire it up for this project's framework:
   - Plain Vite: add `whereami()` to the `plugins` array in vite.config.ts/js.
   - SvelteKit: add `whereami()` to the `plugins` array in vite.config.ts (after
     `sveltekit()`), and add `whereamiHandle()` to `src/hooks.server.ts`, fed with
     what the plugin resolved at build time so it works on edge runtimes:
     `import build from "vite-plugin-whereami/build";`
     `export const handle = whereamiHandle({ ...build });`
     (use `sequence()` from `@sveltejs/kit/hooks` if there are other handlers).
     See https://github.com/mastermakrela/whereami#sveltekit.
3. Check how/where this project deploys (vercel.json, .vercel/, wrangler.toml or
   wrangler.jsonc, CI/CD workflow files). Vercel and Cloudflare Pages are
   auto-detected out of the box, so usually nothing else is needed. If it deploys
   somewhere else, or the default prod/staging/dev mapping doesn't match this
   project's branches/environments, add a custom `detect` option per
   https://github.com/mastermakrela/whereami#environment-detection.
4. Leave favicon tinting, the build-info banner, and the on-screen badge at their
   defaults — don't enable extra options unless asked. Full reference:
   https://github.com/mastermakrela/whereami

Quick start

// vite.config.ts
import { defineConfig } from "vite";
import whereami from "vite-plugin-whereami";

export default defineConfig({
	plugins: [whereami()],
});

That's it. By default:

  • prod (vite build, i.e. mode: "production") — no changes at all.
  • staging (vite build --mode staging) — 🟠 orange favicon tint + 🟠 title prefix.
  • dev (vite dev, or any other/unknown mode) — 🟢 green favicon tint + 🟢 title prefix.

Unknown modes default to dev's tint on purpose — the whole point is to make it obvious when you're not looking at production, so an unrecognized mode should look suspicious, not blend in.

If your project already has a favicon (<link rel="icon"> in index.html, public/favicon.{svg,png}, SvelteKit's static/favicon.{svg,png}, or the src/lib/assets/favicon.{svg,png} path the sv create scaffold uses), it gets tinted (hue/saturation swapped for the environment color, lightness preserved — a shape-preserving recolor, not a flat overlay). If there's none, a simple icon is generated using the first letter of your package.json name.

Environment detection

By default, whereami checks, in order:

  1. the WHEREAMI_ENV environment variable, if set
  2. a few popular hosting platforms' own environment variables:
    • Vercel — VERCEL_ENV: production → prod, preview → staging, development → dev
    • Cloudflare Pages — CF_PAGES/CF_PAGES_BRANCH: the main/master branch → prod, any other branch → staging
    • Node — NODE_ENV=production → prod
  3. Vite's mode: "production" → prod, "staging" → staging, anything else → dev

You can fully replace this with your own function — return any string key, matched against environments:

whereami({
	detect: ({ mode, env }) => {
		// https://developers.cloudflare.com/pages/configuration/build-configuration/#environment-variables
		if (env.CF_PAGES === "1") {
			return env.CF_PAGES_BRANCH === "main" ? "prod" : "staging";
		}
		return mode === "production" ? "prod" : "dev";
	},
});

Custom environments

whereami({
	environments: {
		prod: {}, // no color/titlePrefix = no changes
		staging: { color: "#f59e0b", titlePrefix: "🟠 [staging] " },
		dev: { color: "#22c55e", titlePrefix: "🟢 [dev] " },
		qa: { color: "#a855f7", titlePrefix: "🟣 [qa] " },
	},
	detect: ({ mode }) => (mode === "qa" ? "qa" : undefined) ?? "prod",
});

Any environment whose config has no color gets no favicon change, and no titlePrefix gets no title change — this is how prod stays a no-op by default without special-casing it in the plugin itself.

Favicon options

whereami({
	favicon: {
		enabled: true, // set false to disable favicon tinting/generation entirely
		path: "public/logo.svg", // explicit source; auto-detected otherwise
	},
});

Only .svg and .png sources are supported. .ico (or anything else) falls back to generating the default letter icon, with a console warning.

Build-info banner

Injects <meta> tags and a styled console.log with your package's name, version, and detected environment — in every environment, including prod, unless you disable it:

whereami({
	banner: {
		enabled: true, // master switch
		meta: true, // <meta name="app-name" content="..."> etc.
		console: true, // the console.log banner
		metaPrefix: "app", // -> app-name, app-version, app-environment
	},
});

// or shorthand to disable everything:
whereami({ banner: false });

On-screen badge

A small colored triangle, fixed to the bottom-left corner, in every non-prod environment (same "no color, no badge" rule as the favicon — prod stays a no-op by default). Click it to open a panel with the same info as the console banner, in a <pre> block:

A page with an "Open devtools console" message, a colored triangle in the bottom-left corner, and an open panel showing name, version, environment, and custom metadata as JSON

Dismiss it by clicking the × button, clicking anywhere outside the panel, or pressing Escape.

whereami({ badge: { enabled: true } });

// or shorthand to disable it:
whereami({ badge: false });

Served SVG badge (SvelteKit)

whereamiHandle() can also serve a small SVG status badge at a fixed URL, so a GitLab/GitHub project badge can point at your running deployment and show the live version — no third-party badge service, no CI step keeping a badge image in sync:

whereamiHandle({
	...build,
	badgeEndpoint: true, // or, with the defaults spelled out:
	// badgeEndpoint: {
	// 	path: "/_whereami/badge.svg",
	// 	label: "staging", // default: the environment key
	// 	show: ["version"], // also: "name", "environment"
	// 	color: "#0b7285", // default: the environment's color, else this teal
	// },
});

Point a GitLab project badge at it under Settings → General → Badges:

Badge image URL: https://your-app.example.com/_whereami/badge.svg
Link:            https://your-app.example.com

This endpoint is public and unauthenticated — anyone who knows (or guesses) the path can request it, with no way to gate access here. It deliberately renders only the fields listed in show; it never exposes metadata, environment variables, or anything else. Responses are served with cache-control: no-cache and an ETag (so repeat requests get a cheap 304) — the whole point is that the badge always reflects what's currently deployed, not a cached snapshot.

It only exists on whereamiHandle(), not the Vite plugin: a plain Vite app is just static files in production, with no server left at request time to answer this request.

Custom metadata

Anything you pass to metadata shows up in both the console banner (as a second, inspectable console.log) and the badge's panel, merged with the built-in name/version/environment (which always win on a key clash):

whereami({
	metadata: {
		region: process.env.FLY_REGION,
		commit: process.env.GITHUB_SHA?.slice(0, 7),
	},
});

Values must be JSON-serializable — they're embedded into the injected script at build/dev-server-start time, not read at request time.

SvelteKit

SvelteKit renders pages through its own SSR pipeline and never calls Vite's transformIndexHtml hook, so the injection happens in the vite-plugin-whereami/sveltekit entry point instead, through SvelteKit's own transformPageChunk in hooks.server.ts. The whereami() Vite plugin still belongs in vite.config.ts: it resolves what is only knowable while building and hands it over through vite-plugin-whereami/build:

// vite.config.ts
plugins: [sveltekit(), whereami()];

// src/hooks.server.ts
import { sequence } from "@sveltejs/kit/hooks";
import build from "vite-plugin-whereami/build";
import { whereamiHandle } from "vite-plugin-whereami/sveltekit";

export const handle = sequence(whereamiHandle({ ...build }) /* ...your other handlers */);

build holds:

  • environment — what detect returned at build time, where the CI's env vars are (CF_PAGES_BRANCH, VERCEL_ENV, your own deploy-target variable) and vite build means production. A deployed Worker has none of them at request time, and a compiled binary often has no NODE_ENV. A WHEREAMI_ENV set at request time still overrides it, so one artifact deployed to several environments can name each one. Your own deploy-target variable goes in the plugin's detect:

    whereami({
    	detect: ({ env }) => ({ production: "prod", development: "staging" })[env.DEPLOY_ENV] ?? "dev",
    });
  • pkg — package.json's name and version, with the commit (CI_COMMIT_SHORT_SHA, else git rev-parse --short HEAD) as semver build metadata: 1.4.0+abc1234.

The values are inlined into your hooks.server.ts — always part of the server bundle — so they need no filesystem access at request time, including on edge/isolate runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy). (The plugin can't hand them to whereamiHandle() directly: Vite leaves packages in node_modules out of the server bundle it processes.) The import only resolves with the plugin installed. Without it, call whereamiHandle() without build: it detects the environment at request time and reads package.json off disk — fine on Node; on an edge runtime it logs a one-time warning and labels the app [email protected]. You can also pass environment and pkg yourself. Next to SvelteKit the plugin does nothing else, so SvelteKit doesn't warn about it.

The handle itself is runtime-agnostic either way: it never statically imports a Node builtin (or Buffer, or process), so a Cloudflare Workers deploy needs no nodejs_compat compatibility flag to use it. Node's fs is loaded lazily, only on runtimes that have it, purely to serve that package.json fallback and to read a favicon off disk.

whereamiHandle() takes the same options as the Vite plugin, with two other differences:

  • There's no Vite mode/command at request time (this runs in the already- built, deployed server), so the default detector trusts NODE_ENV=production outright instead of only during serve — the WHEREAMI_ENV env var and the Vercel/Cloudflare Pages checks work exactly the same way.
  • The favicon is inlined as a data: URI in the <link> tag rather than served from a separate emitted file, since there's no Vite build pipeline to emit assets into at request time. A custom favicon.path is still unreachable on edge runtimes without a filesystem; the generated letter icon is used instead, correctly once the name is resolved.

An icon declared in <svelte:head> (the default in sv create scaffolds) and a titlePrefix both keep working across hydration: the existing icon <link> is left in place — renamed to data-whereami-rel rather than removed — so Svelte's head-hydration cursor never lands on a missing node, and the title prefix is re-applied client-side by a small script, since Svelte's compiled <title> sets document.title again once the page hydrates.

Content-Security-Policy

whereamiHandle() injects no inline JavaScript. The title keeper, console banner and badge are served together as one external file at /_whereami/whereami.js (with an ETag and cache-control: no-cache), loaded by a single <script src="/_whereami/whereami.js" defer> in the <head> — so they work under script-src 'self' or SvelteKit's nonce-based csp config, with no nonce and no 'unsafe-inline'. If none of the three is enabled, no script tag is injected and the path isn't served.

Like the badge endpoint, that path is public and answered before your other handlers run, so whatever you pass as metadata is readable without logging in. Two limitations: apps with paths.base can't load it yet (SvelteKit 404s paths outside base before any hook runs), and a fully static or prerendered site has no server left to answer it.

Full options reference

interface WhereAmIOptions {
	detect?: (ctx: {
		mode: string;
		command: "build" | "serve";
		env: Record<string, string>;
	}) => string;
	environment?: string; // replaces the default detection; WHEREAMI_ENV still wins, a custom detect replaces it
	environments?: Record<string, { color?: string; titlePrefix?: string }>;
	favicon?: { enabled?: boolean; path?: string };
	banner?: boolean | { enabled?: boolean; meta?: boolean; console?: boolean; metaPrefix?: string };
	badge?: boolean | { enabled?: boolean };
	metadata?: Record<string, unknown>; // must be JSON-serializable
	packageJsonPath?: string; // default: "package.json"
	pkg?: { name: string; version: string }; // bypasses packageJsonPath/fs when set
	badgeEndpoint?:
		| true // all defaults
		| {
				path?: string; // default: "/_whereami/badge.svg"
				label?: string; // default: the environment key
				show?: Array<"name" | "version" | "environment">; // default: ["version"]
				color?: string; // default: the environment's color, else "#0b7285"
		  }; // SvelteKit only, see "Served SVG badge" above
}

Development

bun install
bun run test          # vitest (unit + integration against fixture vite projects)
bun run build         # tsup -> dist/
bun run typecheck
bun run lint          # oxlint
bun run format:check  # oxfmt --check

playground-app/ is a throwaway Vite project (gitignored) useful for manual checks:

bun run playground         # starts a dev server against playground-app
bun run playground:build   # runs a production build of playground-app

Both rebuild the plugin first, so changes under src/ show up on the next run. To try a different environment, pass --mode, e.g. vite build playground-app --mode staging (or set WHEREAMI_ENV=staging).

Releasing

This repo uses Changesets for versioning/changelogs and npm's OIDC trusted publishing for the actual npm publish — no NPM_TOKEN secret needed, as long as this package's Trusted Publisher on npmjs.com is configured to point at this GitHub repo and the Release workflow.

  1. Run bun run changeset to describe your change (patch/minor/major + summary).
  2. Commit the generated .changeset/*.md file and push to main.
  3. The Release workflow bumps the version and updates CHANGELOG.md (committing that straight back to main, no PR/review step), then publishes to npm with npm publish (the one command that actually supports trusted publishing — bun/pnpm don't yet) — all in the same run.

License

MIT