Stop Clicking the Wrong Icon: A Simple Fix for Chrome Extension Devs

I once lost twenty minutes debugging a “broken” feature.

Turns out… I was testing the production build.

Same icon. Same name. Different environment. Completely my fault.

If you build Chrome extensions long enough, this will happen to you. It’s basically a rite of passage. But it’s also avoidable.

If you’re using a modern framework like Plasmo or Wxt, you’re already halfway there. Here’s how I make sure I never mix up dev and prod again.

The Smart Framework Way with Plasmo

By default, Plasmo turns your development icon grayscale. That tiny visual cue saves you instantly.

No thinking. No guessing.

If you want more control, drop an icon.development.png into your assets/ folder.

assets/
  icon.png
  icon.development.png

Plasmo swaps it automatically during development builds.

Zero manual effort. Zero confusion.

Another simpler Way with Wxt

Wxt doesn’t force grayscale by default, but it gives you better control.

Icons are auto-detected from the public/ directory. You just place files there and Wxt wires them into the manifest.

Basic structure:

public/
  icon-16.png
  icon-48.png
  icon-128.png

Wxt scans this directory and injects them into the manifest automatically.

Option 1: Use the auto-icons module (cleanest)

Install the official module:

npm i -D @wxt-dev/auto-icons

Then enable it:

// wxt.config.ts
export default defineConfig({
  modules: ['@wxt-dev/auto-icons'],
})

Drop a single base icon src/assets/icon.png

Now here’s the useful part: the module can apply grayscale or overlays during development.

That means your dev build can look visually different without you manually swapping files.

You get automatic icon sizing plus a dev-only visual indicator.

Option 2: Environment-based icon swap

Wxt supports env modes (import.meta.env.MODE, DEV, PROD).

So you can change icons at build time.

Example:

public/
  icons/
    dev-48.png
    prod-48.png

Then in your config:

// wxt.config.ts
export default defineConfig({
  manifest: () => {
    const isDev = process.env.NODE_ENV !== 'production'

    return {
      icons: {
        48: isDev ? '/icons/dev-48.png' : '/icons/prod-48.png',
        128: isDev ? '/icons/dev-128.png' : '/icons/prod-128.png',
      },
    }
  },
})

Now, dev and prod builds compile with different icons automatically.

No manual switching.

If you’re using CRXJS, you can dynamically override the icons field in your config using extendManifest based on your environment.

Let the tooling do the work.

The Build Script Swap (No Framework)

Not using a framework?

No problem.

You probably have a dist folder anyway. Something like this

src/
  icons/
    icon-dev.png
    icon-prod.png

dist/

Here’s what I do:

  • Keep icon-dev.png in source.
  • Keep icon-prod.png in source.
  • During production build, copy icon-prod.png → rename to icon.png → place inside dist.

Add a build step:

{
  "scripts": {
    "build": "node scripts/swap-icon.js && vite build"
  }
}

Script:

// scripts/swap-icon.js
import fs from "fs"

const isProd = process.env.NODE_ENV === "production"

const src = isProd
  ? "src/icons/icon-prod.png"
  : "src/icons/icon-dev.png"

fs.copyFileSync(src, "public/icon.png")

That’s it. One command. Always correct icon.

Your source can stay a bit messy. But your production build stays clean.

And most importantly, your toolbar tells you the truth.

Simple automation beats human memory every time.

Dynamic Icon Swapping (If You Want to Be Fancy)

If you like runtime control, you can use the chrome.action.setIcon() API in Manifest V3.

Inside your background script:

  • Check the environment.
  • Look at the version string.
  • Or use a custom flag.

Then call:

chrome.action.setIcon({ path: "dev-icon.png" })

This changes the toolbar icon dynamically.

Important detail:
It won’t change the icon on chrome://extensions.

But in the toolbar, where you actually look it works perfectly.

Save Your Sanity

Here’s the thing.

You only need to get burned once before this matters.

A different icon is a tiny change. But it protects your focus. And focus is everything when you’re building.

Set it up once.

Make it obvious.

And never waste another debugging session clicking the wrong extension again.

Leave a Reply