Tag: chrome extension development

  • How to Build a Deployable Chrome Extension From Scratch in 2026: A UK Developer’s Walkthrough

    How to Build a Deployable Chrome Extension From Scratch in 2026: A UK Developer’s Walkthrough

    Chrome extensions are one of those rare bits of software where the gap between “idea” and “shipped product” is genuinely small. A weekend, a decent text editor, and some patience with the Chrome Web Store review queue is all it takes. But Manifest V3, Google’s current extension platform, has enough sharp edges that I’ve seen experienced developers waste a full day on avoidable mistakes. This walkthrough covers the complete build pipeline: folder structure, service workers, permissions, icon design, the popup UI, and the specifics of publishing through a UK developer account. Let’s get into it.

    Developer building a Chrome extension Manifest V3 project on a laptop at a desk
    Photo by Christina Morillo on Pexels

    What Manifest V3 actually changes

    If you last built a Chrome extension pre-2023, the mental model shift here is real. Manifest V3 replaced persistent background pages with service workers. That means your background script now has a lifecycle, it wakes up, does work, and gets terminated. You cannot store state in a global variable and expect it to persist across events. That tripped me up the first time.

    The other big change is the content security policy and the removal of webRequestBlocking for most developers. Ad blockers were the headline casualty, but for the overwhelming majority of extensions, productivity tools, tab managers, colour pickers, form helpers, none of that matters. What matters is that you use chrome.storage.local or chrome.storage.session instead of memory, and that you structure your service worker around event listeners rather than long-running logic.

    Setting up the folder structure

    Chrome extensions have a flat-ish structure. Here is what I use as a starting point:

    my-extension/
    ├── manifest.json
    ├── background.js
    ├── popup/
    │   ├── popup.html
    │   ├── popup.js
    │   └── popup.css
    ├── content/
    │   └── content.js
    └── icons/
        ├── icon16.png
        ├── icon32.png
        ├── icon48.png
        └── icon128.png

    The manifest.json is the entry point for everything. A minimal but real Manifest V3 file looks like this:

    {
      "manifest_version": 3,
      "name": "My Extension",
      "version": "1.0.0",
      "description": "Does something useful.",
      "permissions": ["storage", "activeTab", "scripting"],
      "background": {
        "service_worker": "background.js"
      },
      "action": {
        "default_popup": "popup/popup.html",
        "default_icon": {
          "16": "icons/icon16.png",
          "32": "icons/icon32.png",
          "48": "icons/icon48.png",
          "128": "icons/icon128.png"
        }
      },
      "icons": {
        "16": "icons/icon16.png",
        "48": "icons/icon48.png",
        "128": "icons/icon128.png"
      }
    }

    Request only the permissions you actually need. The Chrome Web Store reviewers check this, and users see permission prompts. activeTab is far less scary to users than tabs; use the narrower one if you can.

    Chrome extension Manifest V3 service worker code shown in a dark code editor
    Photo by Godfrey Atima on Pexels

    Writing the service worker

    Your background.js registers event listeners at the top level. That is it. Any logic that needs to run when something happens goes inside those listeners.

    chrome.runtime.onInstalled.addListener(() => {
      chrome.storage.local.set({ enabled: true });
    });
    
    chrome.action.onClicked.addListener(async (tab) => {
      const { enabled } = await chrome.storage.local.get('enabled');
      await chrome.storage.local.set({ enabled: !enabled });
    });

    If you need to communicate between the service worker and a content script, use chrome.runtime.sendMessage and chrome.runtime.onMessage. Keep those message payloads small and serialisable, no DOM nodes, no class instances.

    Building the popup UI

    The popup is just an HTML file. It renders in a small window when the user clicks the extension icon, with a max width of 800px and a max height of 600px in practice. I treat it like a tiny web app: semantic HTML, a small CSS file, and a JavaScript module that talks to storage and the background via messages.

    One thing worth knowing: the popup re-renders from scratch every time it opens. Read your state from chrome.storage in a DOMContentLoaded listener, not in a module-level variable. This is where the service-worker mental model bleeds into the popup too, nothing is persistent in memory. For anything more complex than a toggle, I’ve started reaching for a small reactive state pattern. Nothing fancy, just a single render(state) function that updates the DOM whenever storage changes. If you want full component structure, you can bundle a tiny framework like Preact into the popup directory, but for most tools that is overkill.

    On the design side: popup UIs are brutally small. Every pixel is load-bearing. I wrote recently about how most product teams get icon systems wrong, and extension popups are where that really bites, unclear icons in a 400px-wide interface with no room for labels are a usability disaster. Use 20px minimum touch targets, high-contrast text, and a single clear primary action per screen.

    Icon design for the Chrome Web Store

    You need four icon sizes: 16px, 32px, 48px, and 128px. The 128px version is what the Web Store displays on your listing page, so it needs to look polished at that size. The 16px version appears in the browser toolbar, meaning it must be readable as a silhouette, not as a detailed illustration.

    The practical approach I use: design at 128px, then manually redraw the 16px version as a simplified glyph. Do not just scale down the 128px, it will look terrible. Export as PNG with transparency. Avoid thin strokes under 2px at small sizes; they disappear entirely. If you want a deep dive on designing multi-resolution icon sets properly, the icon design guide on this blog is worth your time.

    Testing before submission

    Load your unpacked extension via chrome://extensions with Developer Mode toggled on. Reload it after every change to manifest.json; other file changes sometimes hot-reload, sometimes don’t. Use the service worker’s DevTools (there is an “inspect views” link on the extension card) to debug background script issues.

    Run through this checklist before you zip anything up: all declared permissions are actually used in code; icons exist at all four declared sizes; the popup opens without console errors; storage reads and writes work across a browser restart; and the extension does not break the pages it injects into. That last one sounds obvious, but content scripts can conflict with page CSS in ways that only appear on specific sites.

    Publishing to the Chrome Web Store as a UK developer

    You need a Google developer account, which costs a one-time fee of $5 USD (about £4 at current rates). Pay it once, and you can publish unlimited extensions. The payment goes through Google’s system, so your card needs to be set up for international transactions, most UK bank accounts handle this without any fuss.

    Create a ZIP of your extension directory (not a folder containing it, the manifest.json should be at the root of the ZIP). Upload it through the Chrome Web Store Developer Dashboard. You will need: a 440x280px promotional tile image, at least one 1280x800px screenshot, a short description (132 characters max), and a full description. The review process currently takes between a few hours and five business days for a new submission, Google does not publish a hard SLA.

    If your extension handles any user data, you must complete a privacy disclosure. UK developers should be aware that if you collect or transmit personal data, the ICO’s guidance on browser-based data collection applies to your extension just as it does to any other software product. Worth reading the ICO’s guidance for organisations before you hit publish if your extension touches anything beyond local storage.

    Version bumps are straightforward: update the version field in manifest.json, re-ZIP, and upload a new package in the dashboard. The review cycle for updates is usually faster than for initial submissions.

    Build pipeline considerations

    For a simple extension, you don’t need a bundler. Plain ES modules with "type": "module" work in content scripts and popups. But if you are importing npm packages, you need a bundler, Vite handles Chrome extension builds cleanly with the vite-plugin-web-extension plugin, which manages multi-entry-point builds and hot reloading during development.

    For teams shipping extensions alongside other web products, I’d think about how your extension fits into the broader development workflow. Good tooling compounds, the same efficiency gains that apply to web product pipelines apply here too. R2G.co.uk has an interesting take on how workflow efficiency translates to profitability that’s worth a read if you’re thinking about that side of things.

    For TypeScript users: you absolutely should be using it for anything beyond a toy extension. The Chrome extension types package (@types/chrome) is comprehensive and will save you from a class of runtime errors that are genuinely annoying to debug. If you are new to TypeScript in a design-adjacent context, the TypeScript introduction for UK freelancers on this blog is a good starting point before you wire it into a build pipeline.

    Ship something real. The Chrome extension ecosystem is less crowded than the App Store, the barrier is lower than you think, and a focused tool that solves one problem well consistently outperforms anything that tries to do everything. Pick your itch, build the fix, and get it listed.

    Frequently Asked Questions

    What is Manifest V3 and do I have to use it?

    Manifest V3 is the current version of Chrome’s extension platform, which replaced Manifest V2 with service workers instead of persistent background pages, among other changes. Google has phased out MV2 support, so yes, any new extension you build in 2026 must use Manifest V3, and existing MV2 extensions have been disabled in Chrome.

    How long does Chrome Web Store review take for a new extension?

    Review times vary from a few hours to around five business days for a first submission. Updates to existing extensions are typically reviewed faster. Google does not guarantee a specific turnaround, so factor in review time if you have a launch date in mind.

    How much does it cost to publish a Chrome extension in the UK?

    The one-time Google developer registration fee is $5 USD (roughly £4), paid through Google’s payment system. After that, there are no per-extension fees or annual costs, you can publish as many extensions as you like under the same account.