Browser-extension engineering โ services โ Ahsan Mahmood
Manifest V3 extensions for Chrome, Edge and Firefox, built to survive store review.
Manifest V3 extensions for Chrome, Firefox and Edge โ including the compliance work that decides whether a submission is accepted.
A submission comes back with one question. Why does a content script need access to every site a person visits?
The honest answer is that nobody wrote a justification down when the permission was added.
The code works. The build is clean. An extension fails on its manifest rather than on its feature, and that is the half nobody budgets for.
People arrive here with something that already runs in their own browser and does what they wanted. What it does not have is a manifest anybody has read the way a reviewer reads one, or a listing that explains the permissions in the order a reviewer meets them.
A rejected submission is not a bug report. It is a queue you re-enter at the back, and the reviewer has already read one version of your listing.
The fix is usually a paragraph and a narrower permission rather than a rewrite.
Manifest V3 is the constraint, and it is stricter than most teams expect.
No remote code.
No script pulled from a CDN, no remotely hosted analytics, no eval and no dynamically constructed function, because each of those is grounds for rejection rather than a note in a review. Everything the extension runs has to be inside the package the store received.
The second constraint is that one codebase has to satisfy Chrome, Edge and Firefox, and their manifests differ in places the documentation makes sound identical.
The third is the permission set. Every permission is a sentence a reviewer has to accept, and the ones hardest to justify are the ones a library adds on your behalf.
There is a fourth, and it reaches further than people expect. Sign-in goes through the browser's own identity API rather than a hosted auth SDK, because that SDK would be remote code and the first rule has already answered it.
Telemetry lands in the same place. Whatever error reporting the extension carries is bundled into the package, since a hosted analytics tag cannot be there at all.
WXT is the build tool and TypeScript is the language. What an engagement of this shape covers does not change from project to project.
One build, three stores. Manifest V3 with no remote code. A permission set argued down to what the feature actually needs, with the argument written out rather than assumed.
Store listing material, and the replies when a reviewer asks. And the install and update pages, which live inside the extension rather than on a website, because somebody who has just installed a thing has not agreed to be sent anywhere.
Those two are separate surfaces with separate jobs. One runs on first install and ends with the extension configured. The other runs after an update and says what changed.
Video Controls Plus, ShieldPro Ultimate Ad Blocker and CRXForge are extensions of this shape, and each has its own page on this site.
The permission work happens before the feature work, because a capability that cannot be justified is cheaper to drop at the start than to defend at submission.
A permission argued down is sometimes a feature argued away, and that conversation belongs on the scoping call rather than after the build.
No remote code means no third-party analytics tag and no experiment tool loaded at runtime, so whatever you want to measure is bundled or is nothing. That is a genuine loss. You trade measurement for a package that cannot be rejected for the thing it loads.
Three review queues, three sets of listing text, three chances to be asked a question. My name is Ahsan Mahmood, and all three of those conversations are mine to hold.
There is a smaller cost sitting in the update page. Something has to be written for it on every release, and the release nobody writes for is the one where users find out from a permission prompt.
A finished build is not proof the extension runs. The only way to know the service worker registers is to load the package unpacked and watch it start, which is a manual step at the end of every build that matters.
None of this is dramatic on its own. It is a slower path to a first release than a web app takes, and it is the path that gets accepted.
The exclusions here are the ones that hold across the catalogue. Visual design as a standalone deliverable. Native iOS. Blockchain work. Copywriting and content.
Thirty days of bug fixes at no charge, and after that nothing is automatic.
Every permission that survives is one I had to write a sentence for. The ones I could not are features you will not get, and you hear which ones on the call rather than after the build.
from $800
How long it takes
Small features 1โ2 weeks, an MVP 4โ8 weeks, a full production app 8โ16 weeks. Those are ranges I have hit on work of that shape before. The scoping call settles how many permissions the feature genuinely needs, because the review conversation is shaped by that list rather than by the size of the codebase.
Every engagement of this shape covers
- One build, three stores
- Manifest V3 with no remote code
- Permission set argued down to what the feature needs
- Store listing material and the review responses
- Install and update pages inside the extension
Built with
Which browsers does one extension build cover?
Chrome, Edge and Firefox, from one build. The manifests differ in places the documentation makes sound identical, so the build produces a package per store rather than one file uploaded three times. Safari is not covered, because it is an Apple platform and there is no Apple Developer account here.
Why can an extension not load a script from a CDN?
Because Manifest V3 forbids remote code, and a store treats it as grounds for rejection rather than as a note in a review. Everything the extension runs has to be inside the package the store received. That rules out a remotely hosted analytics tag and any experiment tool fetched at runtime, so whatever you want to measure is bundled or is nothing.
Do you handle the store listing and the rejections?
Yes, and both are part of what every extension engagement covers. The permission set is argued down before submission, and each permission that survives gets a written justification in the order a reviewer meets it. What I cannot do is promise the decision or the date, because that queue belongs to the store.
Does the user see anything after an install or an update?
Yes, and it happens inside the extension rather than on a website. An install page runs once and ends with the extension configured; an update page says what changed. Both ship in the package, so a person who never opens the site still sees them.
How much does a browser extension cost?
$800 USD is the floor for a small, well-defined feature. Everything above that comes from a scoping call, in writing, before any work starts. What moves the number here is the permission set, because each permission that survives needs a written justification and a listing paragraph, and how many of the three stores are in scope.
https://aoneahsan.com/services/browser-extension-engineering