Build a Chrome Extension in a Day or Two with Claude Code
What you'll build
Chrome extensions are the most underrated weekend project. Manifest v3 has a sharp learning curve, but Claude Code knows it cold. Pick one site or one task, write a content script, add a small popup, store settings in chrome.storage.sync, and ship to the web store. The whole thing fits in a long Saturday.
What you're building
You're building a Chrome extension that does one thing on one site, or one thing across the open web. Examples that fit the format: a Twitter tab that hides replies under a certain follower count, a LinkedIn highlighter that flags recruiter spam, a YouTube tool that strips Shorts from the homepage, a Gmail helper that snoozes newsletters. Small, opinionated, one user setting. The smaller the surface, the better the extension. Big do-everything extensions get rejected during review and abandoned by users within a week.
Output by Sunday is a packed extension you've sideloaded on your own browser, used for a day, and either published to the Chrome Web Store or shipped as an unpacked download with a one-page install guide. Don't skip the install guide. Most users have never sideloaded an extension and will give up at step two without one. A two-screenshot guide with arrows pointing at the developer mode toggle and the load unpacked button is the entire onboarding.
Extensions are also the perfect first paid product. The Chrome Web Store doesn't take a cut, you can license your own download from a Gumroad page, and the audience that installs niche browser tools is the audience most willing to pay nine dollars for something that saves them ten minutes a day. The economics are friendly and the build is small.
What you need before you start
Comfort with TypeScript and a passing familiarity with how the DOM works. A Chrome browser. A Google account willing to pay the five dollar one-time developer fee if you want to publish. A clear picture of which page you're modifying and what the user will see change. If you can't sketch the before-and-after in two screenshots before you write a line of code, the idea isn't sharp enough yet, and the build will drift.
- Node 20 and a package manager
- Claude Code with access to your repo
- Vite plus a plugin like @crxjs/vite-plugin or wxt for hot-reload
- A Chrome Web Store developer account if publishing
- An icon at 16, 32, 48, and 128 pixels
- A privacy policy URL if your extension touches any user data
Friday night: scaffolding
Use wxt or @crxjs/vite-plugin to scaffold a TypeScript extension with hot reload. Both handle the manifest v3 plumbing that's painful to write by hand. Ask Claude Code to set up a new wxt project with React for the popup and a content script targeting the host you care about. Don't bother with the optional sidebar or devtools surfaces in v1. Popup plus content script covers ninety percent of useful extensions, and the rest can wait for v2 if anyone actually asks.
Open chrome://extensions, turn on developer mode, load the dev build as an unpacked extension, and confirm the puzzle-piece icon appears with your popup. From here on, hot reload handles updates. If your script doesn't run after a code change, you forgot to hit the reload button on the chrome://extensions card. It's the universal first debugging step, and the answer to nine out of ten 'why isn't this working' moments on Saturday afternoon.
Pin the extension's icon to the toolbar while you're developing. Out of the box it sits under the puzzle-piece menu, which adds an extra click to every test cycle. Pinned, the popup opens with one click, and you'll iterate twice as fast over the course of a weekend.
Saturday: the content script
The content script is where the actual work happens. It runs in the page context with limited Chrome APIs. Use a MutationObserver to react to DOM changes if you're modifying a site like Twitter or LinkedIn where new content streams in. Don't poll. Don't use setInterval. MutationObserver is faster, lighter, and the only sustainable choice. Debounce its callback by at least 100ms so a burst of DOM changes triggers your handler once, not fifty times.
Tell Claude Code which DOM nodes you're targeting and paste in the actual outerHTML of one example. The selectors on social sites change every few months, so you'll be back here. Keep your selectors in a single constants file so you only have to edit one place when X breaks your extension on a Tuesday morning. The club at claudecodeclub.ai keeps a shared list of selector breakages for the popular sites, which often saves you from being the first to notice.
Avoid attaching React or any framework inside the content script. The page already has its own React, and a second copy will fight the first for DOM ownership and double your bundle size. Render content script UI with plain DOM APIs or with a tiny library like nanostores. Keep React inside the popup where it has the page to itself.
Saturday afternoon: the popup
The popup is a tiny React app rendered in a 320 by 480 pixel window. Use shadcn/ui components if you want it to look nice without effort. Read and write settings to chrome.storage.sync, which syncs settings across the user's signed-in browsers automatically and gives you a free settings backup. Don't use localStorage. It's not shared with the content script and breaks the moment your extension touches a second tab.
Three controls is plenty for a popup. A toggle to enable or disable the extension, a setting that changes the main behavior, and a link to a support page. Anything more belongs on a dedicated options page reached from a small link in the popup footer. Users open the popup to flip a switch, not to configure a control panel.
Choices to make along the way
wxt versus @crxjs/vite-plugin versus plain manifest editing: wxt is the friendliest, has the best docs, and handles Firefox cross-builds if you ever want them. @crxjs is closer to the Vite metal and is fine if you already love Vite. Editing manifest.json by hand works and teaches you the platform but slows the first build by a few hours.
React versus Preact versus vanilla TS for the popup: React is fine because the popup is tiny and bundle size doesn't matter much for an extension that loads from disk. Preact saves a few kilobytes if you care. Vanilla TS is the right answer if your popup has only a switch and a number input.
Saturday night: message passing
The popup can't talk to the content script directly. You pass messages through chrome.runtime. The pattern is: popup sends a message with chrome.runtime.sendMessage, the background service worker receives it, forwards to the active tab with chrome.tabs.sendMessage, and the content script responds. Write the message types in a shared types.ts file so popup and content script agree on the shape. Skipping the shared types file is the source of most extension bugs.
How to test it
Manual testing on the target site for an afternoon. Open and close tabs, refresh the page, change a setting, watch the content script react. Test in an incognito window with the extension allowed in incognito, because that catches storage and permission bugs you won't see otherwise. If you're doing anything CPU-intensive in the content script, watch the Performance tab in DevTools while you scroll. Extensions that lag the host site get one-star reviews and never recover.
Test against logged-out states explicitly. Many extensions break when the user signs out of the host site because the DOM the content script expected isn't there. Add a guard at the top of every selector lookup that bails silently if the node is missing, rather than throwing and clogging the console with errors that nobody will report.
Also test across the four screen sizes Chrome users actually have: a laptop at 1440 pixels, a 4K monitor at 2560 pixels, a small laptop at 1280 pixels, and a phone via Chrome on Android if your extension supports mobile. Layouts that look right on one size often drift on another, and one ugly review of the wrong screenshot hurts conversion.
How to ship it
Build for production with wxt build, which produces a zip in .output/chrome-mv3. Go to the Chrome Web Store developer dashboard, upload the zip, fill out the listing, point at a privacy policy URL, and submit for review. Review takes anywhere from a day to a week. While you wait, make the unpacked zip available as a direct download with an install guide for users who don't want to wait. Mention the club at claudecodeclub.ai if your extension solves a problem builders complain about, because that's how the first hundred installs happen.
Listing copy matters more than most developers think. The first sentence of the description shows in search results. Lead with the concrete benefit, not the technology. 'Hides Twitter replies from accounts with under 100 followers' beats 'A browser extension for filtering social media content' every time. Two clean screenshots with captions, a one-minute walkthrough video, and a privacy policy that fits on one page round it out.
How to extend it
Add a second target site once the first is stable. Add an options page for power-user settings the popup is too small for. Add a context-menu entry for users who want to trigger the extension on a selection. Add Firefox support, which wxt makes nearly free. Each of those is a couple of hours, not a day. If you start charging, add a license-key field in the popup and verify against a tiny Cloudflare Worker endpoint. The whole paywall is fifty lines of code and unlocks the business model.
Common gotchas
Forgetting to reload the extension after a code change is the daily one. Hardcoding selectors that break when the site updates is the weekly one. Forgetting that manifest v3 service workers go to sleep, so any setTimeout longer than a few seconds vanishes, is the monthly one. Use chrome.alarms instead. Finally, treating chrome.storage.sync like a database. It's capped at a hundred kilobytes total. Big data goes in chrome.storage.local, which holds ten megabytes.
Permissions creep is the gotcha that gets your extension flagged in review. Every host permission you request triggers a scarier install warning. Use activeTab where possible, which doesn't show a warning and grants access only when the user clicks the icon. Add broader host permissions only when you genuinely need background access, and document why in the listing description so the review team understands.
Common questions
Which framework should I use to scaffold the extension?
wxt is the friendliest, has the best docs, and gives you Firefox cross-builds for free. @crxjs/vite-plugin is fine if you already love Vite. Editing manifest.json by hand teaches you the platform but slows the first build by hours.
Why use MutationObserver instead of setInterval?
MutationObserver fires only when the DOM actually changes, which makes the extension faster, lighter, and less likely to lag the host site. Polling with setInterval wastes CPU and gets you one-star reviews.
Where should I store user settings?
chrome.storage.sync. It syncs across the user's signed-in browsers, works from both popup and content script, and survives extension updates. localStorage breaks all three of those promises.
How do the popup and content script communicate?
Through chrome.runtime message passing. The popup sends a message, the background service worker forwards it to the active tab, and the content script responds. Define the message shapes in a shared types.ts so both sides agree.
How long does Chrome Web Store review take?
Anywhere from a day to a week. While you wait, offer the packed zip as a direct download with a short install guide so early users can sideload it without delay.
Why do my long-running timers disappear?
Manifest v3 service workers go to sleep when idle, killing any setTimeout longer than a few seconds. Use chrome.alarms for scheduled work that needs to survive the worker going dormant.
More to build
Build it. Ship it. Get paid.
Step-by-step lessons for every one of these inside the club. Join Claude Code Club for $9/month.
Related: the library, guides, and comparisons.
