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

make-app-icons

v1.0.0

Published

Master image (SVG or PNG) -> the modern favicon/PWA icon set (Evil Martians' "How to Favicon in 2021": favicon.ico, icon.svg with dark-mode support, apple-touch-icon, maskable + PWA manifest icons, manifest.webmanifest, head snippet), with the classic 16-

Readme

make-app-icons

Turns a master image into the icon files a modern web or app project actually needs. By default that means the file set from Evil Martians’ “How to Favicon in 2021: Six files that fit most needs” — favicon.ico, icon.svg, apple-touch-icon.png, icon-192.png, icon-mask.png, icon-512.png, manifest.webmanifest, and a copy-paste head-snippet.html — in one pass, through sharp alone (resize, compositing, and PNG compression). The classic 16–1024 cascade this tool originally shipped with (a 2026 rewrite of a 2019 personal tool) is still available behind --classic.

npx make-app-icons logo-master.svg
npx make-app-icons logo-master.png --classic --optimize
npx make-app-icons brand-a.png brand-b.png --out dist/icons --sizes 32,180,512

Or install it: npm i -g make-app-icons, then run make-app-icons ....

Hardened: 2026-10-31 · ed59f21 · gate CLOSED WITH FOLLOW-UPS

What it does

Reads one or more master images — an SVG is the best master (see “SVG masters and dark mode” below), a 1024×1024 (or at least 512×512) PNG is the next best thing, and anything else sharp can decode still works — and generates the modern file set by default. Non-square input is letterboxed onto a transparent square rather than cropped, so nothing important gets cut off. Smaller masters still work — they’re upscaled with a warning, since upscaled output can look soft.

With a single input, files land directly in --out. With multiple inputs, each one gets its own subfolder named after its filename (e.g. brand-a.png → icons/brand-a/), so a light/dark or multi-brand run doesn’t overwrite files across inputs. Two inputs that would share a folder — a/logo.png and b/logo.png, or logo.png and logo.svg — are refused before anything is written (exit 1, naming both); rename one or run them separately with their own --out. Names that differ only in case count as the same folder, since the default macOS and Windows file systems treat them as one.

A run that would write an output over one of its own masters — say a 1024 px site/icon-512.png with --out site, or site/icon.svg with --out site — is refused the same way, so the master is never replaced by its own downscaled copy.

The modern default

make-app-icons logo-master.svg

produces the article’s six files, plus the manifest and head snippet it describes pasting them with:

| File | Size | Notes | |---|---|---| | favicon.ico | 32×32 | Single size, no palette (“32 bpp, 8-bit alpha, no palette” per the article) — not the old multi-size ico | | icon.svg | vector | SVG masters only — passed through with an injected dark-mode <style> scaffold; skipped for raster masters | | apple-touch-icon.png | 180×180 canvas | Artwork scaled to 140×140 and centered (20px padding), on a background color | | icon-192.png | 192×192 | Manifest icons entry | | icon-mask.png | 512×512 canvas | Maskable variant — artwork confined to a centered 409×409 safe zone; manifest purpose: "maskable" | | icon-512.png | 512×512 | Manifest icons entry | | manifest.webmanifest | — | The icons array, as its own file | | head-snippet.html | — | The four <link> lines to paste into <head> |

The head snippet, verbatim (the icon.svg line is omitted for a raster master):

<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/manifest.webmanifest">

Paths are root-relative (/icon-192.png) by default, matching the article’s own examples — these six files are meant to live at the site root, not tucked into an /icons subfolder. Pass --base-href <path> if your deployment puts them somewhere else (e.g. --base-href /assets/icons → /assets/icons/icon-192.png).

--no-modern turns this default off (pair it with --classic to get only the old cascade, or with --sizes — though --sizes already skips modern output on its own; see below).

apple-touch-icon.png: padding and background

The article’s guidance: “scale the image itself to 140×140 and set the canvas to 180×180”, because “an Apple touch icon will look better if you place 20px padding around the icon and add some background color.” This tool follows that exactly — 140px content, centered with 20px of padding on every side, on a --apple-bg background (default #ffffff; pass --apple-bg transparent to opt out).

--apple-fill <color> additionally flat-recolors the artwork itself for this one output — the same silhouette-preserving full recolor the environment variants use (style injection for SVG masters, alpha-preserving flat tint for rasters). Its typical use is an inverted tile, e.g. a dark mark that should render as white-on-black on the home screen:

make-app-icons logo.svg --apple-bg "#000000" --apple-fill "#ffffff"

Only apple-touch-icon.png is affected; every other output keeps the master’s own colors.

icon-mask.png: the maskable safe zone

The article’s guidance for maskable icons: “The safe zone is a central circle of 409×409” within the 512×512 canvas, and it points to maskable.app for checking your result against real mask shapes (circle, squircle, rounded square, …), since different platforms crop maskable icons differently.

This tool implements that as a centered 409×409 content square on the 512×512 canvas — the practical reading most maskable-icon tooling uses, rather than the stricter (and rarely followed) geometry of inscribing a square inside the 409-diameter circle. The article doesn’t mandate a background color for the maskable variant, so this defaults to transparent; --maskable-bg <color> overrides it (many real deployments use a solid brand color here, since the OS may not fill transparent regions the way you expect once cropped).

Either way: check your actual result at maskable.app. This tool gets the geometry right; whether your specific artwork reads well once masked is a design judgment call it can’t make for you.

manifest.webmanifest

Written exactly as the article shows it — just the icons array, in the article’s own order:

{
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-mask.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

If your project needs a fuller manifest (name, short_name, start_url, display, theme_color, …), add those fields yourself — the article doesn’t specify them, so this tool doesn’t invent them.

SVG masters and dark mode

Give this tool an .svg master and, on top of the rasterized PNGs above (rendered by sharp directly at each target size — it reads SVG natively, which beats scaling one pre-rasterized 1024 down repeatedly and keeps every size crisp), it also writes icon.svg: your source SVG, passed through verbatim, with a <style> block injected right after the opening <svg> tag:

<style>
@media (prefers-color-scheme: dark) {
  /* ... */
}
</style>

That’s the article’s own dark-mode technique — the article’s worked example gives a shape a class and overrides it in the media query:

<style>
@media (prefers-color-scheme: dark) {
  .a { fill: #f0f0f0 }
}
</style>
<path class="a" fill="#0f0f0f" d="…" />

This tool can’t know which shapes in your SVG should change for dark mode — that’s a hand-tuning problem, not a mechanical one — so it gives you three ways to fill in that block:

  • Nothing (default): an empty, commented scaffold. The <style> block is present so you can see where it goes, but it does nothing until you edit it or pass one of the flags below.

  • --dark-fill <color>: a blunt, naive override — * { fill: <color> !important; } — useful for a quick “does dark mode even work here” check, almost never right for a finished dark variant (it recolors every shape the same way, ignoring the article’s per-shape-class approach). Give a plain CSS color (#f0f0f0, white, rgb(240 240 240), …). The check is on shape, not meaning: a hex value, a word of letters only, or word(…) holding only letters, digits, spaces and . , % / + - passes, and anything else is refused. So a typo such as whte is written as given, and a function with a hex value inside (light-dark(#000, #fff)) is refused.

  • --dark-css <value>: exact control. <value> is either a path to a CSS file or inline CSS text; either way its contents are injected verbatim inside the media query. This is how to reproduce the article’s own .a { fill: #f0f0f0 } pattern — add matching classes to your SVG’s shapes first, then pass the rules that target them.

Each of the three blocks opens with its own comment, so an icon.svg this tool wrote can be fed back in as the master: the old block is replaced, not stacked under a new one. A hand-authored <style> in your master is left as it is.

A raster master never gets an icon.svg. This tool doesn’t trace or fake a vector from a PNG/JPEG source — icon.svg is simply skipped, with a console note and the corresponding <link> line dropped from head-snippet.html. Provide an SVG master if you want this file and dark-mode support at all.

Environment icons (--env)

make-app-icons logo-master.svg --env dev,staging
make-app-icons logo-master.svg --env dev --env qa --env-color qa=#22aa55

Dan Gaidula’s personal convention, carried over from his production Craft CMS projects: non-production environments get their own favicon, flat- recolored to a fixed marker color, so a glance at the browser tab says which environment you’re looking at. Two built-in names cover the common case —

| Environment | Color | |---|---| | dev | #e11d2c (red) | | staging | #0080ff (blue) |

— and --env-color <name>=<color> (repeatable) defines or overrides any name, including ones with no built-in default: --env qa --env-color qa=#22aa55 works, --env qa alone doesn’t (it errors, since there’s nothing sane to guess for an arbitrary name).

--env itself takes a comma-separated list and is repeatable — --env dev,staging and --env dev --env staging are equivalent, and combine if both are used. Names go into file names, so they’re limited to letters, digits, - and _.

Only favicon.ico and icon.svg get variants — favicon-dev.ico, icon-dev.svg, favicon-staging.ico, icon-staging.svg, and so on. Nothing else does: icon-192.png, icon-mask.png, icon-512.png, and apple-touch-icon.png stay production-colored on every environment, by design. In Dan’s Craft projects the manifest and home-screen icons are never what gives an environment away — the tab favicon is — so only the favicon pair gets the treatment.

This is a full flat recolor of the glyph, not a badge or corner flag: every visible pixel becomes the marker color, alpha untouched.

  • SVG masters get it via a single injected style block — <style>* { fill: <color> !important; } [stroke] { stroke: <color> !important; }</style> — rather than rewriting each shape’s fill/stroke attribute. The blunt fill rule wins: it needs no SVG parsing (so it can’t miss a shape whose color comes from a CSS class, currentColor, or a gradient reference), and !important beats the artwork’s own more-specific rules without having to know what they are. It’s the same technique --dark-fill uses above, except here it’s the intended outcome, not a documented fallback. Stroke deliberately does not get the universal-selector treatment: forcing stroke onto elements that never had one enables stroking at the SVG default stroke-width of 1 user unit, which blobs fine detail shut and clips at the viewBox edge. The [stroke] attribute selector recolors real strokes and leaves never-stroked artwork alone.

  • Raster masters get it by discarding their color entirely and keeping only their alpha: extract the alpha channel, fill a solid canvas with the marker color, rejoin that alpha as the new alpha channel. Every opaque pixel becomes exactly the marker color regardless of how many colors the source art used — unlike a hue-preserving tint (which would go muddy on multi-color art), “keep the silhouette, replace every color with one flat color” can’t fail that way, so raster masters get a real recolor here, not a skip notice. The silhouette is the alpha, though: a master with no transparency (a JPEG, or a PNG on a solid background) recolors to a solid square, and the run warns when that happens — for --env and for --apple-fill, which uses the same recolor.

Dark mode is deliberately stripped from environment variants, not just left unconfigured. Even if the same run passes --dark-fill or --dark-css for the production icon.svg, the environment variants never carry a prefers-color-scheme: dark block — this tool strips any existing one (its own injected scaffold, or a hand-authored one already in the master, including the typed <style type="text/css"> form Illustrator and Inkscape write) before writing the variant. A dev/staging marker that quietly turns white under dark browser chrome would stop being a marker; env variants are hardcoded to their flat color on purpose, everywhere.

head-snippet.html gets a commented block documenting the variant hrefs (not live conditional markup — Dan’s Craft projects switch the icon pair server-side by environment, so the snippet’s job is just to show the variant files exist and what they’re named).

--classic: the old 16–1024 cascade

make-app-icons logo-master.png --classic

produces the full cascade this tool shipped with before this upgrade:

| Size | Filename | Platform / use | |---|---|---| | 16×16 | favicon-16.png | Browser tab favicon (low-density) | | 32×32 | favicon-32.png | Browser tab/bookmark favicon (high-density) | | 48×48 | favicon-48.png | Windows “site icons” pinned-site / taskbar use | | 64×64 | icon-64.png | General small app icon, legacy Windows icon size | | 128×128 | icon-128.png | macOS Finder small icon, Chrome Web Store icon, Linux desktop icon | | 180×180 | apple-touch-icon.png | iOS/iPadOS home-screen bookmark icon | | 192×192 | icon-192.png | Android/Chrome PWA install icon | | 256×256 | icon-256.png | macOS icon, Windows large icon, store listing thumbnails | | 512×512 | icon-512.png | Android splash-screen source, Play Store icon | | 1024×1024 | icon-1024.png | App Store / Play Store submission master, macOS .icns source |

--classic combines with the modern default (on by default, so make-app-icons logo.png --classic runs both). Where the two would write the same filename — apple-touch-icon.png, icon-192.png, icon-512.png — the classic cascade skips its own plain version and leaves the modern, purpose-built one (padded apple-touch-icon, etc.) in place. Run --classic --no-modern for the old cascade exactly as it was, with no deduping.

The old --ico (multi-size favicon.ico) and --manifest (manifest-icons.json fragment) flags still work, paired with --classic — modern mode already emits its own favicon.ico and manifest.webmanifest, so those flags no-op with a notice unless --classic is also given.

Custom sizes

--sizes overrides modern and classic output entirely with a comma-separated list of pixel sizes, output as generic icon-<size>.png files — this is the one flag that bypasses everything else in this file (--env variants and --optimize still apply):

make-app-icons logo-master.png --sizes 16,32,64,512
make-app-icons logo-master.png --sizes 16,32,64,512 --ico --manifest

An empty list (--sizes ',') is refused rather than writing nothing.

--optimize: re-optimize PNGs with lithocinch

make-app-icons logo-master.svg --optimize

If the lithocinch CLI is on PATH (own tool; npm i -g lithocinch or npm link from a clone), this re-runs its perceptually-lossless PNG optimizer over every generated PNG after the fact — sharp’s built-in palette quantization is decent, but lithocinch measures per-image against an SSIM target instead of guessing. Invocation, for the curious:

lithocinch <staged-pngs> -o <staged-out> -s '' --overwrite --format png --no-resize

--format png locks the format (lithocinch’s own default would happily convert these to WebP/AVIF, which breaks apple-touch-icon.png and the manifest’s "type": "image/png"); --no-resize guarantees dimensions are preserved; the PNG engine is left at lithocinch’s own default — adaptive palette quantization with a 0.92 SSIM floor, falling back to lossless, which is exactly the “measured, not guessed” perceptually-lossless mode this needs. Only the PNGs are touched; icon.svg, favicon.ico, manifest.webmanifest, and head-snippet.html are left alone.

lithocinch is never a package dependency — it’s discovered on PATH at runtime only. If it isn’t found, --optimize prints a notice and falls back to the sharp palette quantization already applied during generation (the same thing --no-minify controls).

Compression

PNG output is written through sharp’s built-in palette quantization (palette: true) by default — the same class of lossy, reduced-color-palette compression pngquant did in the original 2019 tool, but without pulling in imagemin or its (now largely unmaintained) plugin ecosystem. Pass --no-minify to skip quantization and keep full truecolor PNGs. favicon.ico’s source render always skips quantization regardless of this flag — the article calls for “no palette” there specifically.

Options

-o, --out <dir>         Output directory (default: ./icons)
-s, --sizes <list>      Comma-separated custom pixel sizes, e.g. 16,32,64,512
                        Overrides modern + classic output entirely.
    --no-modern         Skip the modern six-file output (on by default)
    --classic           Also emit the classic 16-1024 icon cascade
    --base-href <path>  Href prefix for manifest.webmanifest / head-snippet.html
                        (default: '', i.e. root-relative "/icon-192.png")
    --dark-fill <color> icon.svg dark mode: naive global fill override
    --dark-css <val>    icon.svg dark mode: inline CSS, or a path to a CSS
                        file, injected verbatim inside the media query
    --apple-bg <color>  apple-touch-icon.png canvas background
                        (default: #ffffff; "transparent" for none)
    --apple-fill <color> apple-touch-icon.png artwork recolor: flat-recolors
                        the mark itself (silhouette preserved), e.g.
                        --apple-bg "#000000" --apple-fill "#ffffff"
    --maskable-bg <color> icon-mask.png canvas background
                        (default: transparent)
    --env <list>         Comma-separated environment names (dev,staging,...);
                        repeatable. Emits favicon-<env>.ico and
                        icon-<env>.svg only, flat-recolored - no PNG
                        variants. dev/staging have built-in colors
    --env-color <n>=<c>  Set/override an environment's marker color
                        (repeatable), e.g. --env-color qa=#22aa55
    --optimize          Re-optimize PNG outputs with the lithocinch CLI, if
                        found on PATH (falls back to the sharp quantizer)
    --ico               [classic] Also emit a multi-size favicon.ico
    --manifest          [classic] Also emit manifest-icons.json
    --manifest-path <p> [classic] Path prefix for manifest-icons.json "src"
    --no-minify         Skip palette-quantized PNG compression, keep truecolor
    --cheat             Print a short card of common invocations in tldr page format and exit
-h, --help               Show help

Known limits

  • PNG (and one ICO, one SVG) output only. There’s no JPEG/WebP output mode.

  • A CRLF SVG master gives mixed line endings in icon.svg. The injected dark-mode block uses LF while the master’s own lines keep CRLF. Browsers don’t care; normalize the file if your repo does.

  • icon.svg requires an SVG master. Raster masters get every PNG/ICO output, just not icon.svg — this tool never traces or fakes a vector from a raster source.

  • The maskable safe zone is a practical approximation, not the strict circle-inscribed-square geometry — see “icon-mask.png” above. Verify your result at maskable.app.

  • --optimize needs lithocinch on PATH to do anything beyond the sharp fallback. It’s never installed as a dependency of this tool.

  • Classic .ico sizes are capped by convention, not by --sizes. If none of your custom sizes land on 16/32/48/64/128/256, classic --ico skips with a warning rather than forcing an unconventional size into the container.

  • --env’s icon-<env>.svg needs an SVG master, same as icon.svg itself — a raster master still gets favicon-<env>.ico, recolored via the alpha-preserving tint described above, just not the SVG half of the pair.

  • Flat recolors of a raster master need transparency. --env and --apple-fill keep only the master’s alpha, so an opaque master gives a solid square. The run warns but still writes it.

  • --dark-css is trusted input. Its value goes into icon.svg verbatim, and a value that isn’t a readable file is used as literal CSS — a mistyped path (darkk.css) is injected as text, with no warning. Unlike --dark-fill, it isn’t validated.

  • --base-href takes a root-relative path, not a URL. An absolute URL such as https://cdn.example.com/icons comes out as /https://cdn.example.com/icons/icon-192.png.

  • Very large declared SVG sizes fail. An SVG whose width/height declare something like 20000×20000 stops with sharp’s “Input image exceeds pixel limit”; give the master a smaller declared width and height (the viewBox can stay as it is).

  • A flag’s value is whatever comes next. A value flag given last is refused, but --out --classic takes --classic as the folder name.

  • Same-folder detection is case-insensitive everywhere, including on case-sensitive Linux file systems, where Logo.png and logo.png would in fact get separate folders; such a run is refused.

Requirements

Node 20.9+ (sharp 0.35 needs 20.9, png-to-ico 3 needs 20).

Credits

The modern icon stack this tool builds by default follows Evil Martians’ “How to Favicon in 2021: Six files that fit most needs” — the file list, the sizes, the apple-touch-icon padding, the maskable safe zone, the dark-mode <style> technique, all of it traces back to that article. This tool takes their ideas, adds a few of Dan Gaidula’s own conventions on top — environment-variant icons (--env) among them — and automates the whole thing into one pass over a master image.

License

MIT