A social knowledge tool for researchers built on ATProto
Browser Extension Setup with Plasmo#
This guide walks through setting up a browser extension using Plasmo framework while reusing existing webapp components and code.
Overview#
We'll use Plasmo (Option 3) which provides:
- Built specifically for modern web extensions
- Excellent TypeScript support
- Hot reloading during development
- Handles Manifest V3 complexities
- Easy component sharing
Setup Steps#
1. Install Plasmo#
cd src/webapp
pnpm add -D plasmo
2. File Structure#
Plasmo uses convention over configuration. Extension files go directly in the webapp root:
src/
webapp/
app/ # Keep existing Next.js app
components/ # Keep existing components
hooks/ # Keep existing hooks
api-client/ # Keep existing API client
popup.tsx # Extension popup (root level)
options.tsx # Extension options page (root level)
content.ts # Content scripts (root level)
background.ts # Background script (root level)
package.json # Update this
3. Update package.json Scripts#
{
"scripts": {
"dev": "next dev",
"dev:extension": "plasmo dev",
"build": "next build",
"build:extension": "plasmo build",
"package:extension": "plasmo package"
}
}
4. File Structure#
Plasmo uses convention over configuration. Extension files go directly in the webapp root:
src/
webapp/
app/ # Keep existing Next.js app
components/ # Keep existing components
hooks/ # Keep existing hooks
api-client/ # Keep existing API client
popup.tsx # Extension popup (root level)
options.tsx # Extension options page (root level)
content.ts # Content scripts (root level)
background.ts # Background script (root level)
package.json # Update this
5. Create Extension Entry Points#
popup.tsx:
import { useAuth } from "./hooks/useAuth"
import { Button, Center, Title } from "@mantine/core"
import "@mantine/core/styles.css"; // Reuse your styles
function IndexPopup() {
const { isAuthenticated, login } = useAuth()
return (
<Center>
<Title order={1}>Your App Extension</Title>
{isAuthenticated ? (
<div>Welcome back!</div>
) : (
<Button onClick={() => login()}>Sign In</Button>
)}
</Center>
)
}
export default IndexPopup
content.ts:
import type { PlasmoCSConfig } from 'plasmo';
export const config: PlasmoCSConfig = {
matches: ['<all_urls>'],
};
// Add save-to-library functionality to pages
console.log('Content script loaded');
6. Update Your Auth Hook for Extension#
hooks/useAuth.tsx:
// Add extension-specific storage
const isExtension = typeof chrome !== 'undefined' && chrome.storage;
const useAuth = () => {
// Use chrome.storage for extension, localStorage for webapp
const storage = isExtension ? chrome.storage.local : localStorage;
// Rest of your existing auth logic...
};
7. Shared Component Configuration#
Create shared/components/ and move reusable components:
// Move these to shared/components/
-UrlCard.tsx - ui / components;
Update imports in both webapp and extension to use shared components.
8. Development Workflow#
Terminal 1 - Webapp:
cd src/webapp
pnpm dev
Terminal 2 - Extension:
cd src/webapp
pnpm dev:extension
9. Load Extension in Browser#
- Run
pnpm dev:extension - Open Chrome → Extensions → Developer mode
- Click "Load unpacked" → Select
src/webapp/build-extension - Extension auto-reloads on code changes!
10. Handle Extension-Specific APIs#
background.ts:
import { ApiClient } from './api-client/ApiClient';
// Handle extension-specific functionality
chrome.action.onClicked.addListener((tab) => {
// Save current page to library
});
Key Benefits of This Setup#
- ✅ Live Reloading: Plasmo provides hot reload for extension development
- ✅ Code Reuse: Share components, hooks, and API client
- ✅ TypeScript: Full TypeScript support
- ✅ Separate Builds: Webapp and extension build independently
- ✅ Modern Tooling: Uses your existing build tools
Development Commands#
# Develop webapp
pnpm dev
# Develop extension (with live reload)
pnpm dev:extension
# Build extension for production
pnpm build:extension
# Package extension as .zip
pnpm package:extension
Notes#
- The extension will automatically reload in the browser when you make changes to any extension files
- Use chrome.storage for extension-specific data persistence
- Share as much code as possible between webapp and extension
- Test in both Chrome and Firefox during development