A Chrome extension is a small distributed application. Its popup, content scripts, extension pages and service worker run in different contexts, with different permissions and lifecycles.
The most reliable way to build one is to start with the user task and the minimum browser capabilities it needs. AI-assisted coding can speed up implementation, but it should not invent permissions, move secrets into the client or assume that a background process stays alive.
This guide covers the core decisions for a Manifest V3 extension.
1. Choose the smallest useful architecture
A typical extension may contain:
- Popup or side panel: the interface the user opens.
- Content script: narrowly scoped interaction with a matching web page.
- Service worker: event-driven coordination and privileged extension API calls.
- Options page: settings that need more space than a popup.
- Shared modules: validation, message types and domain logic.
Do not add every surface by default. If the extension only needs a toolbar popup and one user-triggered action, start there.
Keep page-specific DOM work in content scripts. Keep privileged browser operations in the extension context. Pass data between contexts through validated messages rather than sharing assumptions about global state.
Choose an extension toolchain
For a TypeScript extension, WXT is one option for organising entry points, building extension bundles and targeting supported browsers. It builds on Vite and can reduce the amount of manifest and bundler plumbing you maintain yourself.
It is not a requirement. A small extension may be easier to maintain with a direct Vite setup or a minimal build script. Choose a framework when its conventions solve a real problem for the project, not because a template includes more features.
Before adopting any starter, inspect the generated manifest, permissions, content-script matches, build output and update process. Keep the resulting extension package understandable without relying on undocumented framework behaviour.
2. Declare permissions deliberately
Manifest V3 separates API permissions from host permissions. Request only the capabilities the feature needs.
A minimal manifest might look like this:
{
"manifest_version": 3,
"name": "Example helper",
"version": "1.0.0",
"action": {
"default_popup": "popup.html"
},
"permissions": ["storage"],
"background": {
"service_worker": "service-worker.js",
"type": "module"
}
}
Add host permissions only when the extension genuinely needs access to particular sites or origins. Prefer optional permissions when access can be requested at the moment a user enables the relevant feature.
A broad permission can increase user friction and the impact of a security flaw. It should have a clear product justification.
3. Design for service worker shutdown
An extension service worker is not a permanently running background page. Chrome can stop it after a period of inactivity and start it again when an event arrives.
Do not rely on in-memory variables for durable state. Register event listeners at the top level and persist required state using the extension storage API or IndexedDB.
For example:
chrome.runtime.onInstalled.addListener(async ({ reason }) => {
if (reason === "install") {
await chrome.storage.local.set({ enabled: true });
}
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message?.type !== "GET_SETTINGS") return;
chrome.storage.local.get(["enabled"]).then(sendResponse);
return true;
});
The example is intentionally small. In a real extension, validate message shape, sender identity and the data being returned. Handle rejected storage operations and avoid treating messages from content scripts as trusted input.
See Chrome's service worker lifecycle guidance and storage documentation.
4. Keep content scripts isolated and narrow
Content scripts interact with the page DOM, but they should not trust page content. A page can contain hostile or malformed data, and the page's JavaScript environment is not the extension's privileged environment.
Use explicit message schemas. Validate URLs and identifiers. Avoid injecting HTML from page content. Prefer DOM APIs and text content over string-built markup.
If a feature needs to inspect a page, explain what is read and when. Do not collect page contents in the background merely because a permission makes it possible.
5. Keep executable code inside the package
Manifest V3 does not support remotely hosted executable code. Bundle the code the extension runs and treat remote responses as data, not scripts.
Do not use eval, the Function constructor or remote script loading as a shortcut for generated functionality. An AI assistant should never add those patterns to make a feature easier to prototype.
Chrome's Manifest V3 migration guide covers the changes to background execution, permissions, network request handling and extension security.
6. Test the extension in its real contexts
A normal web-app unit test is not enough. Test the extension as an installed unpacked build in Chrome.
At minimum, verify:
- first install and upgrade behaviour;
- service worker termination and restart;
- popup open/close and keyboard use;
- content script matching and non-matching pages;
- permission granted, denied and revoked;
- offline and failed network requests;
- storage failures and corrupted state;
- messages from unexpected tabs or frames;
- the exact packaged build, not just a development server.
Keep unit tests for pure functions and validation. Add browser-level tests for the important user journeys. Use Chrome's extension service worker inspector and extension errors page when debugging lifecycle issues.
7. Prepare the Chrome Web Store submission
Before submission, verify the package, permissions, listing and privacy disclosures against the actual implementation. The listing must accurately describe functionality. Any data collection or handling must be disclosed consistently with the extension and the applicable Chrome Web Store policies.
Do not claim that an extension is private, secure or locally processed unless the architecture supports that statement. Review every third-party SDK and API endpoint.
A useful AI-assisted workflow
Use AI to draft a manifest, explain unfamiliar APIs, generate tests and review a focused diff. Give it the current manifest and the exact feature requirement. Ask it to justify each permission and identify every data boundary.
Reject patches that add broad host access, remote executable code, hidden collection or unnecessary dependencies. Run the extension in Chrome and inspect the real behaviour before release.