Skip to main content

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.

Comments