Build a Chrome Extension
Ship a Chrome extension with user auth, cloud storage, and payments
What You'll Build
A published Chrome extension with user authentication, cloud data storage, premium tier with Stripe payments, and a landing page for distribution.
- Working Chrome extension with popup and content scripts
- User auth and cloud storage via Supabase
- Premium features gated behind Stripe payments
- Landing page on Vercel and listing on Chrome Web Store
Prerequisites
- Node.js 18+ installed
- VS Code or Cursor installed
- A GitHub account
- JavaScript/TypeScript knowledge (intermediate)
- A Chrome browser for testing
Architecture
The Chrome extension runs locally in the browser with a popup UI, content scripts for page interaction, and a background service worker for persistent logic. Supabase handles user auth (via OAuth popup) and stores user data in Postgres. Stripe manages premium subscriptions through a checkout flow hosted on your Vercel landing page. GitHub stores the code and triggers Vercel deployments.
Plan checks and sources
Plan-specific access matters more than a tool’s “free” label. Read the dated profile linked above, then confirm your seat count, usage, billing basis and required export or publishing feature.
No new vendor price or free-plan allowance is asserted in this edit. Linked profiles retain their own source dates and unknowns.
Before launch: use a synthetic record, verify permissions and output, test a failed action, and document how to reverse it. A successful checkout preview or API response does not prove a paid order, delivery or confirmed subscription.
Scaffold extension with manifest.json and popup
Set up the Chrome extension project structure with Manifest V3, a popup UI, and the basic file scaffolding.
- Create a new project directory and open it in VS Code
- Create the
manifest.jsonfile with Manifest V3 format - this is the extension's config file - Create
popup.htmlandpopup.jsfor the extension's popup UI - Create a
styles.cssfor your popup styling - Load the extension in Chrome: go to
chrome://extensions, enable Developer Mode, click "Load unpacked", and select your project folder
Illustrative code, not executed in this review. Check current SDK documentation, authentication, input validation and error handling before use.
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0",
"description": "A useful Chrome extension",
"permissions": ["storage", "activeTab"],
"action": {
"default_popup": "popup.html",
"default_icon": "icon-48.png"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}],
"icons": {
"16": "icon-16.png",
"48": "icon-48.png",
"128": "icon-128.png"
}
}
Illustrative code, not executed in this review. Check current SDK documentation, authentication, input validation and error handling before use.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<h1>My Extension</h1>
<div id="auth-section">
<button id="login-btn">Sign In</button>
</div>
<div id="main-section" style="display:none">
<p>Welcome! Extension is active.</p>
<button id="action-btn">Do Something</button>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
Illustrative code, not executed in this review. Check current SDK documentation, authentication, input validation and error handling before use.
// Background service worker
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed')
})
// Listen for messages from popup or content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_DATA') {
// Handle data requests
sendResponse({ status: 'ok' })
}
return true // Keep message channel open for async
})
chrome://extensions open in a pinned tab during development. Click the refresh icon on your extension after every change to reload it.Build the core feature in content and background scripts
Implement your extension's main functionality using content scripts for page interaction and the background service worker for logic.
- Create
content.js- this runs on every page the user visits and can read or modify the DOM - Use
chrome.runtime.sendMessage()in the content script to communicate with the background worker - In
background.js, handle messages and usechrome.storage.localto persist data between sessions - Build the popup UI logic in
popup.js- query the active tab and display relevant data - Test thoroughly: load the extension, visit a page, open the popup, and verify all communication works
Illustrative code, not executed in this review. Check current SDK documentation, authentication, input validation and error handling before use.
// Content script - runs on web pages
(function() {
// Example: extract page data
const pageData = {
title: document.title,
url: global.location.href,
text: document.body.innerText.substring(0, 1000)
}
// Send data to background script
chrome.runtime.sendMessage(
{ type: 'PAGE_DATA', data: pageData },
(response) => console.log('Saved:', response)
)
})()
global.postMessage if you need to communicate with page scripts.Set up Supabase for user data and auth
Add user authentication and cloud data storage so users can sync their extension data across devices.
- Create a Supabase project at supabase.com and grab your project URL and anon key
- Install the Supabase JS client by including it via CDN in your popup.html or bundling it
- Implement Google OAuth login in your popup - Supabase handles the OAuth flow, you just need to open the auth URL
- Create a
user_datatable in Supabase to store user-specific extension data - Enable Row Level Security so users can only access their own data
Illustrative code, not executed in this review. Check current SDK documentation, authentication, input validation and error handling before use.
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://your-project.supabase.co'
const supabaseKey = 'your-anon-key'
export const supabase = createClient(supabaseUrl, supabaseKey)
export async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: chrome.identity.getRedirectURL()
}
})
return { data, error }
}
export async function saveUserData(userId, data) {
return await supabase
.from('user_data')
.upsert({ user_id: userId, ...data })
}
chrome.identity.getRedirectURL() for your OAuth redirect - Chrome provides a special URL for extension auth flows that avoids CORS issues.Create landing page on Vercel and publish to Chrome Web Store
Build a landing page for your extension, push your code to GitHub, and submit to the Chrome Web Store.
- Create a Next.js or static landing page with: hero section, feature list, pricing, and a "Add to Chrome" CTA button
- Deploy the landing page to Vercel by connecting your GitHub repo
- Add your Stripe webhook endpoint and checkout API routes to the Vercel project
- Package your extension: zip the extension folder (excluding node_modules and .git)
- Go to the Chrome Web Store Developer Dashboard, pay the $5 registration fee, upload your zip, add screenshots and descriptions, and submit for review
Illustrative code, not executed in this review. Check current SDK documentation, authentication, input validation and error handling before use.
# Package extension for Chrome Web Store
cd my-extension
zip -r ../my-extension.zip . -x "node_modules/*" ".git/*" "*.map"
Validate before launch
Target outcome: A published Chrome extension with user authentication, cloud data storage, premium tier with Stripe payments, and a landing page for distribution. Check the actual workflow, failure and recovery paths before publishing.
Need help building?
I help founders set up, connect, and launch their tool stacks. Skip the guesswork.
Work with me →