DEV Community

Cover image for I Got Tired of Chrome Extension Setup Hell — So I Built This TypeScript Boilerplate (Ready to Use)
POP SEVEN
POP SEVEN

Posted on

I Got Tired of Chrome Extension Setup Hell — So I Built This TypeScript Boilerplate (Ready to Use)

Every time I wanted to build a Chrome Extension, I'd spend the first 2–3 hours doing the exact same thing:

  • Fighting Manifest V3 config
  • Wiring up TypeScript with @types/chrome
  • Figuring out how the popup talks to the background service worker
  • Setting up Vite for multi-entry builds
  • Creating the options page storage logic

Before writing a single line of my actual idea.

So I stopped doing that. I packaged everything into a boilerplate I can clone and start building immediately — and I'm sharing it here.

What's Included

chrome-extension-boilerplate/
├── manifest.json              ← Manifest V3, fully configured
├── vite.config.ts             ← Multi-entry build (popup, bg, content, options)
├── tsconfig.json              ← Strict TypeScript
└── src/
    ├── types.ts               ← Shared types across all contexts
    ├── utils/
    │   ├── storage.ts         ← chrome.storage.sync helpers
    │   └── messaging.ts       ← Type-safe message passing
    ├── popup/                 ← Dark UI, toggle, live tab URL
    ├── background/            ← Service worker + message router
    ├── content/               ← Content script + toast notifications
    └── options/               ← Settings page, API key field
Enter fullscreen mode Exit fullscreen mode

The Part That Always Broke Me: Messaging
The trickiest part of Chrome Extensions is getting the three contexts — popup, background service worker, and content script — to talk to each other reliably. Here's how the boilerplate handles it:

Shared types (src/types.ts):

export type MessageType =
  | "GET_SETTINGS"
  | "SAVE_SETTINGS"
  | "PING"
  | "GET_PAGE_DATA";

export interface MessageRequest {
  type: MessageType;
  payload?: unknown;
}

export interface MessageResponse {
  success: boolean;
  data?: unknown;
  error?: string;
}
Enter fullscreen mode Exit fullscreen mode

Sending from popup to background (src/utils/messaging.ts):

export function sendToBackground(request: MessageRequest): Promise<MessageResponse> {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(request, (response: MessageResponse) => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
        return;
      }
      resolve(response);
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

Handling it in the background worker:

chrome.runtime.onMessage.addListener(
  (request: MessageRequest, _sender, sendResponse) => {
    handleMessage(request).then(sendResponse);
    return true; // Keep channel open for async response
  }
);
Enter fullscreen mode Exit fullscreen mode

That return true at the end? Took me an embarrassing amount of time to figure out the first time. It keeps the message channel open for async responses — without it, everything silently fails.

Getting Started in 5 Minutes

# 1. Download and extract the boilerplate
# 2. Install dependencies
npm install

# 3. Start dev mode (auto-rebuilds on save)
npm run dev

# 4. Load in Chrome
# → chrome://extensions → Developer Mode ON
# → Load unpacked → select the /dist folder
Enter fullscreen mode Exit fullscreen mode

Your extension is live. Now just edit the source files and it rebuilds automatically.

Adding AI / OpenAI Integration
One of the most common things people add to Chrome Extensions right now is AI. Here's all you need:

npm install openai
Enter fullscreen mode Exit fullscreen mode

Then in your background service worker:

import OpenAI from "openai";
import { loadSettings } from "../utils/storage";

async function runAI(prompt: string): Promise<string> {
  const settings = await loadSettings();
  const client = new OpenAI({ apiKey: settings.apiKey });

  const response = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: prompt }],
  });

  return response.choices[0].message.content ?? "";
}
Enter fullscreen mode Exit fullscreen mode

The options page already has an API key field wired to chrome.storage.sync — so users can enter their key and it's securely stored locally.

Publishing to the Chrome Web Store
Once you're ready to ship:

npm run build
Enter fullscreen mode Exit fullscreen mode

Zip the dist/ folder, head to the Chrome Web Store Developer Dashboard, pay the one-time $5 fee, upload, and submit. Review takes 1–3 days.

Get the Boilerplate
The full source is available here — grab it, build something, ship it.

👉 Download on Gumroad — $29

MIT licensed. Use it in unlimited personal and commercial projects.

If this saves you even one hour of setup time, it's worth it. And if you build something cool with it, drop a link in the comments — I'd love to see what you make.

Top comments (0)