Publishing a Firefox extension: where review gets stuck in the catalog
The package is built, signed, and submitted for review — and a few days later it comes back with a one-line "does not comply with policy." What exactly is violated is unclear from the wording: either automation flagged minified code, or the reviewer did not see justification for extended permissions.
The release cycle stalls: the build is ready, but it cannot be published, and review timelines in the catalog are not announced in advance and depend on the queue and the type of changes.
The reasons a package is returned are almost always technical and predictable. Dynamic code execution, loading external scripts, obfuscation without accompanying source code and reproducible build instructions are a ready reason for rejection.
Separately, they catch a discrepancy between declared permissions and actual behavior: if the add-on requests access to tabs but the description says nothing about it, add-on moderation will return the package for revision.
The review itself happens in two layers: automated checks for dangerous calls and manual review for those builds where the meaning of the behavior needs to be assessed.
Any change in permissions or addition of external calls moves the update into extended review, so a pinpoint edit costs as much time as a major release. Resubmitting without fixing the root cause leads to the same result and shifts the queue.
We explain how this is resolved: we assemble the package to catalog requirements, prepare source code and explanations for reviewers, and handle correspondence until publication — so the cycle does not go into a second and third round.
Effect of a streamlined release: timelines, rejections, reproducibility
The first measurable effect is a predictable review timeline. When a package is assembled according to a single regulation (manifest, justification for each permission, absence of remotely loaded code, data processing declaration), review stops coming back over formalities.
The review cycle in the Mozilla catalog becomes planned: one submission instead of a series of iterations "edit — wait — edit".
The second effect is a decline in the rejection rate. A return for revision is usually caused not by code logic but by expanding permissions during an update, minified code without accompanying source, and data collected without a declared purpose.
The rule "a new permission is added only for a specific function, with an explanation in the description" removes this class of rejections almost completely — only isolated cases remain.
The third effect is a reproducible build. Pinned dependency versions, a single build tool, and deterministic file order in the archive yield the same result for any engineer on the team.
The hash of the built package matches what went to the catalog, so rollback to the previous signed release takes minutes, not rebuilding from memory.
The fourth is a transparent release. The changelog is written in the user's language, stable and test channels are separated, and each update is confirmed by a signature. The team sees exactly what users received, and support sees which version a request came from.
Delivery methods to the catalog: manual, automatic, separate channel
An extension reaches users via several paths, and the choice among them determines who is responsible for updates and code review.
The main route is the Firefox Add-ons catalog: it distributes updates automatically and checks the build before release, but each version must be built, signed, and passed through review.
The delivery method is chosen by release frequency and by how rigidly manual control is needed at each step. Below are four working options and their limitations.
| Option | When suitable | Limitations |
|---|---|---|
| Manual package submission | infrequent releases, one or two per quarter; control needed at each step | version depends on a person, build and upload steps are easy to miss |
| Automatic build and submission | regular releases, reproducible build, automated tests available | access keys stored securely; if checks fail, the release is blocked entirely |
| Self-hosted update channel | distribution inside a company or together with another product | update hosting, signing, and compatibility remain on you, installation occurs outside the catalog |
| Temporary loading for tests | checking the build before publication, debugging on a live profile | lives until browser restart, unsigned, unavailable to users |
In practice, the options are combined: during development, temporary loading is convenient; for release, automatic submission to the catalog; and the self-hosted channel is left for closed environments where installation from the catalog is impossible.
Extension preparation stages: from audit to handover
Catalog reviewers disassemble the extension package in parts: they unpack the archive, compare the minified code with the sources, and check why each permission and each domain is requested.
Therefore, "just upload the archive" turns into a cycle of edits and resubmissions that drags out the release timeline. We run preparation as a project with fixed stages — the output is a package that passes review on the first or second attempt.
- Package audit. We analyze the current build: which permissions are actually used, whether remotely loaded code exists, whether minified sections remain, how the final archive is assembled.
- Bringing the manifest to catalog requirements. We narrow permissions to those actually needed, remove dynamic script loading, and bring the description, icons, and data processing statement to the card format.
- Update channel scheme. We determine which builds go to the public channel, which to the closed test channel, and how version numbers relate, so a user's transition from the test branch to the release branch goes without rollbacks.
- Preparing sources for review. We assemble a readable version of the code and reproducible build instructions — the reviewer must receive exactly the archive described in the instructions.
- Submission for review and support. We register a draft, respond to comments, and make pinpoint edits without a full rebuild and loss of version history.
- Release automation and handover. Build, signing, and upload of a new version go through one scenario, so the archive contents and version number do not diverge. We hand over the channel scheme and the procedure for responding to comments — the team releases updates itself.
Deliverables: settings, policies, documentation
The deliverable is not an archive with sources but a working system: from it, an engineer who did not participate in development releases an update.
Each document answers one question — why the extension needs this permission, how to build a valid archive, what to check before submitting for review.
- Permission policy. Table "permission — function — code location": each is justified, unused ones are removed. Most returns from review in AMO are about a discrepancy between declared rights and actual behavior, so the list of rights and the listing description are checked line by line.
- Update release regulation. Step-by-step procedure: bump the version, build the archive, run validation, submit, wait for review, record the result. It separately notes which types of changes pass automated review and which go to manual review.
- Build documentation. Build commands, the list of excluded files, where the sources are and how to attach them if the archive is minified or built by transpilation.
- Pre-submission checklist. Version and build number do not diverge, the manifest references only existing files, permissions match the policy, the description and screenshots are current, and the privacy policy is published.
- Listing template. Short and full description, category, keywords — a draft that only needs to be updated for new functionality.
- Rollback procedure. What to do if a defect occurs after publication: how to remove the problematic version, release a fix, and notify users through the update description.
The documents live in the repository next to the code, not in correspondence, so they live exactly as long as the extension lives. We hand them over together with access to the developer account — the customer's team performs further publications independently.
Case: reduced the review cycle and eliminated repeat returns
Initial situation
The extension was built into a single minified file, and the manifest requested access to all tabs and external hosts "just in case." In such a configuration, Firefox Add-ons review cannot be sure that the code does only what is declared, so every submission returned with a new list of comments.
Iterations reached four — first permissions, then sources, then a discrepancy between the card and actual behavior; the update release timeline stretched to three weeks.
What changed
We rebuilt the manifest: we left minimal host permissions, and moved part of the logic from persistent permissions to a request at the moment of user action.
We made the build reproducible — dependencies are pinned, steps are described, and the source archive matches the built package.
We ran the linter and a check for prohibited techniques (external code execution, obfuscation) before submission, and brought the card description in line with actual data collection.
| Metric | Before | After |
|---|---|---|
| Review iterations per release | 3-4 | 1 |
| Share of submissions with return | about 60% | less than 10% |
| Update release timeline | up to 3 weeks | 4-5 days |
Now the update passes on the first attempt: the reviewer confirms the declared rights and sees that the sources match the build.
Repeat returns have almost disappeared, and the release date is no longer unpredictable — milestone planning again relies on the calendar, not on correspondence with review.
What to do if the package has already been rejected by a moderator?
Package rejection is not a sentence but a list of specific comments: the moderator indicates which catalog policy points are violated and what exactly needs to be fixed.
Most often these are requested permissions that do not match actual functionality, externally loaded code, or undisclosed processing of user data.
The first step is to read the letter in full and write out the comments point by point, without trying to guess the reviewer's motive.
Next is analysis of the comments with verification against the code.
We reproduce the build from the sources, compare the manifest with the actually used interfaces, and remove permissions without which the extension works: it is precisely the discrepancy "we ask for more than needed" that causes the rejection of most packages.
We separately go through external connections and remotely executed code — the catalog requires that all logic reside inside the package and be reviewable by the reviewer.
We make edits pinpoint: a minimal set of changes, without incidentally rewriting neighboring modules, so that repeat review does not open new questions.
We bump the version in the manifest and prepare a short explanation for submission — one line per comment, indicating the file and the essence of the edit. This way the reviewer does not need to compare the old and new build blindly.
Resubmission goes through the same channel, as a regular update, together with this explanation.
If a comment is disputable or ambiguously worded, it is more useful not to resubmit the package but to reply in the same correspondence: calmly, point by point, with a reference to the platform documentation and a request to clarify which exact policy point was applied to a specific place in the code.
Resubmitting an identical build without a reply lengthens review and increases the chance that the next review will be stricter.
Contacting the moderator is a working tool, not a conflict. Formulate the question narrowly and to the point, record the answers in the project documentation: then the same comments will not repeat on the next update.
Technical doubts: permissions, manifest, updates
Doubts before starting usually concern three things: whether the list of requested rights will scare the user, whether the update will break already installed copies, and what exactly the review checks. Below is how we address each of these questions in the project.
Can one get by with a minimal set of permissions?
Yes, and this is a basic principle. We start from the scenario: if a function works through the active tab and explicitly listed hosts, a request for access to all sites is not needed.
In the description for each right we give the reviewer a justification — why the user needs it, not "that's how it was built".
What to do if the extension is built on an old manifest branch?
We migrate it to the current one: the background script becomes a service worker, and the loaded parts are moved inside the build.
If the product is not ready for the transition, we leave the current branch and fix a deadline by which the migration must be completed so that publication does not stall.
How do updates reach installed copies?
We publish the version — the browser pulls it for users on its own, without their participation. For checks we keep a separate channel: the build goes to a closed list of testers, and enters the general stream only after confirmation. Rollback is done by re-uploading the previous version, while the build number always increases.
Can code be loaded from a server?
No, and we build this limitation into the architecture from the start. All executable code must reside inside the package — review rejects external scripts and dynamic string parsing.
We move logic that depends on configuration into data that the extension receives rather than executes.
Let's discuss the task: we'll send an estimate and release plan
To make the task estimate substantive rather than "plus or minus", it is enough to attach several artifacts to the request.
From them, the scope of manifest edits, the state of the build, and the probability of return from review are visible — from this, a release plan with stages and control points is formed.
What to attach to the request:
- A link to the repository or an archive with sources and a build command that reproduces from scratch.
- The current manifest: requested permissions and the justification for each — in Firefox reviewers look at the minimally necessary set.
- A list of network requests and external services the extension communicates with, and what data goes there.
- A draft of the store card: name, description, interface screenshots, localization — they are checked together with the code.
- A link to the privacy policy or its draft, if user data is collected.
- Developer account data and the role on behalf of which the publication is made: without a confirmed owner, publishing cannot be started.
- Preferences for automation: manual version upload or build and submission for review by repository tag.
We prepare some materials ourselves and show them before work starts — the absence of an item from the list is not a reason to postpone the conversation.
Describe the task as it is: we will return with a task estimate and release plan showing the stages, owners, and points where your decision is needed.







