Chrome Extension TypeScript Extensions — Developer Guide
4 min readTypeScript for Chrome Extensions
TypeScript brings type safety, autocompletion, and confident refactoring to Chrome extension development.
Overview
Why TypeScript for Extensions
- Type safety — catch mismatched message shapes at compile time
- Autocompletion —
chrome.tabs.queryreturns fully typedTab[]objects - Confident refactoring — rename fields, verify with
tsc
Chrome API Types
Install @types/chrome for all Chrome extension API types:
npm install --save-dev @types/chrome
Project Setup
mkdir my-extension && cd my-extension
npm init -y
npm install --save-dev typescript @types/chrome esbuild
mkdir src
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"outDir": "dist",
"rootDir": "src",
"types": ["chrome"]
},
"include": ["src"]
}
Build Pipeline
esbuild (Recommended)
import * as esbuild from 'esbuild';
import { copyFileSync, mkdirSync, existsSync } from 'fs';
const entries = [
{ in: 'src/background.ts', out: 'background.js' },
{ in: 'src/popup.ts', out: 'popup.js' },
{ in: 'src/content.ts', out: 'content.js' },
];
const config: esbuild.BuildOptions = {
bundle: true, sourcemap: true, target: 'chrome110',
outdir: 'dist', format: 'iife',
};
async function build() {
if (!existsSync('dist')) mkdirSync('dist');
copyFileSync('manifest.json', 'dist/manifest.json');
await Promise.all(entries.map(e =>
esbuild.build({ ...config, entryPoints: [e.in], outfile: `dist/${e.out}` })
));
}
build();
Typing Chrome APIs
Automatic Types
const tabs = await chrome.tabs.query({ active: true });
// tabs is typed as chrome.tabs.Tab[]
Typed Message Passing
Define messages as discriminated unions:
type MsgFromContent = { type: 'FETCH_URL'; url: string };
type MsgFromBg = { type: 'URL_DATA'; title: string };
type AllMessages = MsgFromContent | MsgFromBg;
const resp = await chrome.runtime.sendMessage<AllMessages, MsgFromBg>({
type: 'FETCH_URL', url: 'https://example.com'
});
chrome.runtime.onMessage.addListener((msg: AllMessages, s, reply) => {
if (msg.type === 'FETCH_URL') reply({ type: 'URL_DATA', title: 'Ex' });
return true;
});
Use @theluckystrike/webext-messaging for simpler typed wrappers.
Typed Storage
interface ExtensionSettings {
theme: 'light' | 'dark';
notifications: boolean;
}
async function getSettings(): Promise<ExtensionSettings> {
const result = await chrome.storage.local.get('settings') as { settings?: ExtensionSettings };
return result.settings ?? { theme: 'light', notifications: true };
}
@theluckystrike/webext-storage provides typed get/set helpers.
Context-Specific Types
Content Scripts (DOM)
{ "compilerOptions": { "lib": ["ES2020", "DOM"] } }
Access chrome.runtime, chrome.storage—not chrome.tabs. Use messaging.
Service Workers (WebWorker)
{ "compilerOptions": { "lib": ["ES2020", "WebWorker"] } }
Summary
- Install
@types/chromefor automatic Chrome API typing - Use esbuild for fast builds; Vite for framework UIs
- Define message types as discriminated unions
- Create storage interfaces instead of using
any - Use separate tsconfigs: DOM for content scripts, WebWorker for service workers
Cross-references:
docs/guides/architecture-patterns.md— structuring extensionsdocs/guides/ci-cd-pipeline.md— automated buildsdocs/guides/debugging-extensions.md— debugging typed code
Related Articles
Related Articles
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.