Best Boilerplates for Chrome Extensions with Backend in 2026
·StarterPick Team
chrome-extensionsaasplasmoboilerplate2026
Chrome Extensions + SaaS Backend
A Chrome extension that syncs with a web application is a powerful product pattern:
- Notion Web Clipper — Saves content to your Notion workspace
- Grammarly — Checks writing everywhere, syncs settings to account
- LastPass — Manages passwords, syncs vault to backend
- 1Password — Same pattern
The challenge: extension and web app use different auth contexts. The extension needs to share the user's session and communicate with the SaaS API.
Architecture: Extension ↔ Backend
Chrome Extension (Plasmo)
├── popup.tsx → Account status, quick actions
├── content scripts → Inject UI into pages
└── background.ts → API calls, token management
⟵ HTTPS API calls ⟶
Your SaaS Backend (Next.js API Routes / tRPC)
├── /api/extension/auth → Verify extension session
├── /api/extension/sync → Sync data from extension
└── /api/extension/user → Get user settings/features
Auth: Extension ↔ Web App Session
The cleanest pattern: user logs into the web app, the extension reads the session cookie.
// background.ts — Read web app session from browser cookies
import { Storage } from '@plasmohq/storage';
const storage = new Storage();
async function getAuthToken(): Promise<string | null> {
// Read session cookie set by your Next.js app
const cookies = await chrome.cookies.getAll({
domain: 'yourapp.com',
name: 'next-auth.session-token',
});
if (!cookies.length) return null;
return cookies[0].value;
}
// Verify session token with your backend
async function getUser(): Promise<User | null> {
const token = await getAuthToken();
if (!token) return null;
const response = await fetch('https://yourapp.com/api/extension/auth', {
headers: { 'Cookie': `next-auth.session-token=${token}` },
});
if (!response.ok) return null;
return response.json();
}
// API route — verify extension request
// app/api/extension/auth/route.ts
import { getServerSession } from 'next-auth';
export async function GET(req: Request) {
const session = await getServerSession();
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 });
return Response.json({
user: session.user,
features: await getUserFeatures(session.user.id),
subscriptionTier: await getSubscriptionTier(session.user.id),
});
}
Plasmo + Next.js Boilerplate Structure
my-saas-extension/
├── apps/
│ ├── web/ # Next.js web app (auth, billing, dashboard)
│ └── extension/ # Plasmo Chrome extension
│ ├── popup.tsx # Extension popup (login state, features)
│ ├── background.ts # API calls, storage management
│ └── contents/
│ └── overlay.tsx # Content script injected into pages
└── packages/
├── api/ # Shared tRPC router
└── types/ # Shared TypeScript types
Feature Gating in Extensions
// popup.tsx — Gate features behind subscription
function Popup() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
getUser().then(u => { setUser(u); setLoading(false); });
}, []);
if (loading) return <Spinner />;
if (!user) return <LoginPrompt />;
return (
<div>
<UserAvatar user={user} />
{user.subscriptionTier === 'free' ? (
<UpgradePrompt feature="Advanced AI Analysis" />
) : (
<PremiumFeatures user={user} />
)}
</div>
);
}
Manifest V3 Required Permissions
{
"manifest_version": 3,
"permissions": [
"cookies", // Read session cookies for auth
"storage", // Local extension storage
"activeTab" // Access current tab for content scripts
],
"host_permissions": [
"https://yourapp.com/*", // Your SaaS API
"https://*.yourapp.com/*" // Subdomains
]
}
Compare Chrome extension and SaaS boilerplates on StarterPick.
Check out this boilerplate
View Plasmo + Next.js on StarterPick →