Three separate projects, because that's the actual state of browser extension development in 2026 — there is no single codebase that ships to all three stores unmodified.
zverts-extensions/
├── chrome/ — Vite + CRXJS, Manifest V3, ready to `npm run build` and load unpacked
├── firefox/ — Vite + CRXJS (firefox target), same MV3 shape, signed via web-ext/AMO
└── safari/ — web-extension-src/ is the INPUT to Xcode's safari-web-extension-converter,
not a standalone buildable project (see safari/README.md)
declarativeNetRequeststatic ruleset (src/rules/blocked_sites.json) redirects blocked domains to a bundledblocked.html- A popup toggle turns the ruleset on/off and persists state in
storage.local - Domains to block are hardcoded in the ruleset JSON for this template —
in a real build you'd want this list fetched from your backend per-user
(so people can configure their own blocklist from your app, not just
edit a local JSON file) and pushed into a dynamic ruleset via
updateDynamicRulesinstead of a static one
- Chrome:
externally_connectablelets zverts.com callchrome.runtime.sendMessage(extensionId, ...)directly — seechrome/README.md - Firefox / Safari: no such API exists. Both use a content-script
relay (
window.postMessage<->runtime.sendMessage) — see the respective READMEs. Functionally equivalent from your website's point of view, just more moving parts under the hood
Repeating this because it matters more than the code: none of these three builds can stop a user from disabling the extension, using a different browser, or using their phone instead. This is a friction/accountability tool. If your product messaging promises "blocks distracting sites" full stop, without the "user has to opt in and can turn it off" caveat, you're setting up a support/trust problem. Mobile enforcement (iOS Screen Time APIs, Android AccessibilityService) is a separate, native-code project — not something any of these three folders touch.