A one-command CLI that turns one image into every icon an Expo app needs, with app.json updates included
In development since March 2026
The chore it removes
Every Expo project needs the same set of icons in the same set of sizes. The official docs walk through making each one separately in Figma or Photoshop, exporting at the right size, dropping into assets/images/, and editing app.json to point at them. It takes ten minutes per project, you make a typo in one path half the time, and the adaptive-icon background colour you eyeball never quite matches the actual dominant colour of the image.
add-expo-icon does all of that in one command.
๐ Usage
npx add-expo-icon ./my-logo.png
That's the whole interface. The script:
- Validates the source (must be at least 500ร500, PNG or JPEG).
- Resizes to all the standard targets through
sharp. - Writes the resized files into
assets/images/in the current directory. - Extracts the dominant colour from the source image using
sharp's pixel statistics. - Rewrites
app.jsonif present โ setsexpo.icon,expo.android.adaptiveIcon.foregroundImage,expo.android.adaptiveIcon.backgroundColor,expo.web.favicon, andexpo.splash.image. - Removes legacy
android-icon-*files that older Expo SDKs generated and that confuse current SDKs if left behind. - Prints a per-file summary with sizes and the extracted colour.
Output looks like:
Dominant colour: #1a1a2e
Source: /path/to/my-logo.png (1024x1024 png)
โ icon.png (1024x1024) โ iOS & general app icon
โ adaptive-icon.png (1024x1024) โ Android adaptive icon foreground
โ favicon.png (48x48) โ Web favicon
โ splash-icon.png (200x200) โ Splash screen icon
All icons saved to assets/images/
โ app.json updated with icon paths
What gets written into app.json
| Key | Value |
|---|---|
expo.icon |
./assets/images/icon.png |
expo.android.adaptiveIcon.foregroundImage |
./assets/images/adaptive-icon.png |
expo.android.adaptiveIcon.backgroundColor |
extracted dominant colour as a hex string |
expo.web.favicon |
./assets/images/favicon.png |
expo.splash.image |
./assets/images/splash-icon.png |
expo.splash.backgroundColor |
extracted dominant colour |
The script is non-destructive โ if app.json doesn't exist or doesn't have an expo key, it skips the rewrite and just produces the icons.
Why sharp
Three reasons. First, it's fast โ single-image resize through libvips beats every alternative on Node. Second, the pixel-statistics API is a one-liner for dominant-colour extraction. Third, sharp is well-maintained and the binary builds for every OS Expo developers actually use.
The script ships with sharp as a dependency rather than as a peer-dependency because the install size on a CLI tool isn't the same constraint as on an application โ npx add-expo-icon is allowed to take an extra five seconds the first time as long as it just works.
๐ ๏ธ Tech Stack
| Category | Stack |
|---|---|
| Runtime | Node.js 18+ |
| Image processing | sharp |
| Distribution | npx-runnable, single bin entry |
| Language | TypeScript (compiled to JS for distribution) |
Notes on the dominant-colour extraction
The first version sampled the centre pixel and called it the dominant colour. That worked for logos with one big block of colour and failed for everything else (gradients, intricate details, transparency). The current version downsamples to a 16ร16 thumbnail, runs k-means with k=4 on the resulting pixels, and picks the cluster centroid with the largest population that isn't pure white, pure black, or fully transparent. It's still a heuristic but it's a much better one โ for the kind of simple logos most apps use, it produces a colour that actually matches what a designer would pick.
Add Expo Icon is a Node CLI that takes a single source image (PNG or JPEG, minimum 500ร500) and produces all the icon variants an Expo project needs โ `icon.png` 1024ร1024, `adaptive-icon.png` 1024ร1024, `favicon.png` 48ร48, and `splash-icon.png` 200ร200. It also rewrites `app.json` to point at the new icons, sets adaptive-icon and splash-screen background colours from the dominant colour of the source image, and cleans up legacy filenames. Built with Node.js and sharp. In development since March 2026.