What You’ll Learn

  • How to structure a Chrome extension starter with TypeScript
  • The minimum files needed for a Manifest V3 extension
  • How to think about popup, content scripts, and service workers
  • How to package the extension for the Chrome Web Store
  • What to keep simple in version one

If you want a Chrome Extension TypeScript starter that can go from zero to the Chrome Web Store, the key is keeping the project small but real.

That means:

  • a clear Manifest V3 setup
  • one runtime role for each piece of logic
  • TypeScript across the codebase
  • a packaging path that does not depend on wishful thinking

The goal is not just to load the extension locally once. It is to get to something you could actually publish.

Starter Structure

Here is the shape I like:

extension/
  src/
    background/
      index.ts
    content/
      index.ts
    popup/
      index.ts
      popup.html
    shared/
      messages.ts
  manifest.json
  package.json
  tsconfig.json

This is enough for a real starter.

Manifest V3 Starter

Start with a minimal but useful manifest.json:

{
  "manifest_version": 3,
  "name": "My TypeScript Extension",
  "version": "1.0.0",
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage", "activeTab", "scripting"],
  "host_permissions": ["https://*/*", "http://*/*"],
  "content_scripts": [
    {
      "matches": ["https://*/*", "http://*/*"],
      "js": ["content.js"]
    }
  ]
}

Do not over-ask on permissions in the starter. The smaller the permission surface, the easier everything is later.

Use Shared Typed Message Contracts

This is one of the best improvements you can make early.

export type ExtensionMessage =
  | { type: 'SAVE_NOTE'; payload: { text: string } }
  | { type: 'GET_NOTE' };

Then your background worker can implement the contract cleanly:

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
  if (message.type === 'SAVE_NOTE') {
    chrome.storage.local.set({ note: message.payload.text }).then(() => {
      sendResponse({ ok: true });
    });
    return true;
  }

  if (message.type === 'GET_NOTE') {
    chrome.storage.local.get('note').then((result) => {
      sendResponse({ ok: true, note: result.note ?? '' });
    });
    return true;
  }
});

This helps a lot once the extension grows beyond toy examples.

Decide What Runs Where

This is the most important architecture rule in browser extensions.

Use the popup for explicit user interaction.

Content script

Use the content script for page-aware DOM behavior.

Background service worker

Use the background worker for extension-level coordination, storage, and browser events.

If these responsibilities blur together, the extension gets messy very quickly.

Build for Local Testing First

Before you think about the Chrome Web Store, make sure the extension works through the normal local flow:

  1. build the files
  2. open chrome://extensions
  3. enable developer mode
  4. load the unpacked extension
  5. test the popup, content script, and background behavior

This sounds obvious, but many starter guides rush past the one part that actually tells you whether the project is shaped correctly.

Packaging for the Chrome Web Store

Once the extension works locally, the Web Store step becomes much easier.

The practical requirements are simple:

  • build the final extension files
  • ensure the manifest is production-ready
  • verify permissions and copy are honest
  • package the extension directory into a zip for upload

The exact listing details change over time, but the core engineering work does not: the extension should already be clean, permission-conscious, and easy to explain.

That is why I prefer building a small publishable starter instead of a tutorial-only demo.

Final Thought

The best Chrome Extension TypeScript starter is not the one with the most boilerplate. It is the one that gives you a clean Manifest V3 foundation, typed runtime boundaries, and a realistic path to shipping on the Chrome Web Store.

Keep the starter small, separate the responsibilities, and build something you could actually upload.

If you need help building browser extensions, TypeScript tooling, or fast product workflows that can go from prototype to release, take a look at my portfolio: voidcraft-site.vercel.app.