← All writing

What It Took to Publish My Browser Extension on Chrome and Firefox

How I published a browser extension on Chrome and Firefox, fixing manifest errors, privacy disclosures, permission reviews, and store approval issues.

I thought building the browser extension would be the hardest part. Then I tried to publish it.

After working on video playback, MPEG-DASH streams, Picture-in-Picture, and in-browser MP4 muxing, Video Player & Reels Controls for Facebook™ was finally ready to share. It worked as a personal project; I wanted other people to be able to install it without manually loading an unpacked extension.

That meant publishing on two platforms: Firefox Add-ons and the Chrome Web Store.

I expected to upload a ZIP file, write a description, and submit it. Instead, I ran into manifest validation errors, unfamiliar source-code questions, permission justifications, privacy disclosures, image requirements, and a warning that appeared after the Chrome version was already published.

Neither store was just an upload button. Here is what actually happened, how I approached the problems, and what I would do differently next time.

Two browsers, two different release packages

The extension is primarily written in JavaScript, but Chrome and Firefox don't have identical extension environments. The Chrome build uses Manifest V3 with a background service worker. My Firefox package uses its browser-specific background configuration and includes Gecko metadata.

That difference made one principle important early on: don't assume the file named manifest.json in a development folder is automatically the right manifest for the store you're uploading to.

I maintain browser-specific manifest sources, then ensure the package being uploaded contains the intended configuration as manifest.json at the ZIP root.

Before submitting, I checked the release archive for the referenced scripts, icons, stylesheets, and the locally bundled video playback library. An extension can pass some static checks and still fail at runtime if a required file was left out of the ZIP.

Challenge 1: Firefox rejected the manifest

My first major Firefox validation error was direct:

The data_collection_permissions property is missing.

For new extensions submitted to Mozilla Add-ons since November 3, 2025, Firefox requires a data-collection declaration under browser_specific_settings.gecko. The missing key was not a playback bug. It was a store-specific manifest requirement.

The required shape is similar to this:

{
  "browser_specific_settings": {
    "gecko": {
      "id": "smooth-video-player@sujon.dev",
      "data_collection_permissions": {
        "required": ["none"]
      }
    }
  }
}

Here, none is not a universal value to copy into every extension. It is appropriate only when the extension's actual behavior satisfies Mozilla's definition of no required data collection or transmission. An extension that sends data outside the browser for additional processing may need a different declaration. The declaration and published privacy policy must tell the same story.

I also learned that changing manifest.firefox.json alone would not help if the release script still put an outdated manifest into the ZIP. Mozilla validates the final packaged manifest.json—not whichever source manifest I had edited most recently.

Lesson: validate the built artifact, not just the source tree.

For the current requirements, I used Mozilla's browser_specific_settings documentation.

Challenge 2: A Shaka Player warning looked more serious than it was

Firefox's validator also reported:

webview.hostObjects is not supported.

The warning pointed inside the bundled Shaka Player JavaScript used for adaptive streaming. I had to distinguish an unsupported API reference in a third-party bundle from an actual runtime failure in my extension.

I didn't want to edit a minified vendor file merely to silence a warning. Such changes can make future upgrades harder and create differences from the upstream library. The more useful questions were: Is the reference guarded by a feature check? Does DASH playback work in Firefox? Is this a warning or a submission-blocking error?

Lesson: not every validator warning calls for a code change. Understand the code path and test the affected feature in the target browser.

Challenge 3: Do I need to upload source code?

Mozilla then asked whether the extension used code generators, minifiers, bundlers, or tools that transform source files into distributable code.

That was less straightforward than I expected. My first-party extension files were readable JavaScript, while Shaka Player was a bundled third-party dependency. Those aren't necessarily the same situation under source-submission rules.

I reviewed which files were original source, which were generated, and whether I could identify the exact upstream version of any third-party library. For reviewer notes, a generic repository homepage is less useful than a version-matched source reference. If first-party source is bundled or minified, reviewers may need the unprocessed source and reproducible build instructions.

The takeaway is not "always choose Yes" or "always choose No." It is know exactly what you are shipping and be able to explain how it was produced.

Mozilla documents the requirements in its source code submission guide.

Challenge 4: Even the Firefox listing URL had a validation limit

After the manifest problems, a much smaller issue stopped me: the initial add-on URL slug exceeded the length accepted by the submission form.

I shortened it to a readable, topic-focused slug:

facebook-video-player-reels

This was a reminder that a product has several separate identities: the display name, the manifest ID, and the public listing slug. They serve different purposes and shouldn't be treated as interchangeable.

I kept the public extension name as Video Player & Reels Controls for Facebook™, rather than changing the brand just to fit a URL field.

Challenge 5: Writing a store listing is part of product design

The next task wasn't really code. I needed to explain the extension to people who had never seen my project.

That included a concise summary, a detailed description, categories, support details, screenshots, and a privacy policy. I wanted the listing to be searchable for relevant terms such as Facebook video player, Reels controls, Picture-in-Picture, and playback speed—without turning it into keyword stuffing.

Screenshots were a separate challenge. My Chrome listing used polished promotional graphics, but the Firefox version has differences in Picture-in-Picture behavior. A screenshot must not suggest that a Firefox user will receive a Chrome-only floating interface.

Lesson: good store SEO helps people discover an extension, but clear, accurate screenshots help them decide whether to trust and install it. Marketing graphics should never promise functionality the actual build does not provide.

Challenge 6: Chrome asked me to justify almost every permission

After Firefox approval, I moved to the Chrome Web Store. I registered the developer account, uploaded the Chrome package, and prepared the listing. The dashboard initially refused to let me submit.

The "Unable to publish" list asked for a single-purpose description and individual justifications for the permissions in my manifest, including:

  • storage — player preferences, optional watch history, and resume information.
  • downloads — user-initiated downloads supported by the extension.
  • scripting — loading packaged playback functionality when needed.
  • contextMenus — opening supported links through a right-click action.
  • declarativeNetRequest — narrowly scoped request handling for relevant Facebook media resolution.
  • Host permissions — access to Facebook pages and associated media domains needed for playback.

I couldn't simply write "required for the extension" in every field. I needed to explain what each permission did, where it was used, and how it related to the extension's single purpose.

The warning about host permissions also made sense: broad website access deserves careful scrutiny. I reviewed whether the requested hosts were actually required by the code and whether each could be explained accurately.

Google's Privacy practices dashboard documentation specifically asks developers to state an extension's single purpose and justify its permissions.

Challenge 7: "Remote code" does not mean every network request

The Chrome dashboard asked whether I was using remote code, and this was easy to misunderstand because my extension makes network requests to retrieve Facebook media.

But the important distinction is between fetching data and fetching executable JavaScript or WebAssembly to run inside the extension.

The extension uses media URLs and manifests as playback data. The streaming engine itself is intended to be packaged within the extension ZIP. Under that architecture, video streams and DASH manifests are not, by themselves, remotely hosted executable code.

That distinction matters because Chrome Manifest V3 has strict restrictions on remotely hosted code. I needed to confirm the packaged library and inspect the code for remote script loading or evaluation before making a declaration.

Lesson: answer store security questions from the release artifact and actual runtime behavior—not assumptions about what words like remote might mean.

Challenge 8: Local storage still deserves an honest privacy policy

Privacy was one of the most valuable lessons from the whole process.

At first, it was tempting to describe the extension as "100% local." That sounds reassuring, but it isn't precise enough. The extension stores preferences and viewing information locally and makes requests to Facebook and its media delivery infrastructure. Some requests also happen through prefetching before a video is played.

There are several distinct storage behaviors: optional watch history, resume positions, temporary stream caches, and a mini-player handoff record that may remain available across navigation. The controls for clearing history do not necessarily clear every kind of temporary or handoff state.

I created a separate public support repository for issues and privacy information. It hosts issue reporting and the privacy policy, which explains these details more carefully.

The Chrome Web Store also required data-use declarations and policy certifications. I treated those as statements about what the software actually does, not just boxes to check to unlock the Submit button.

Lesson: "No developer-operated tracking" is a more specific statement than "No data handling." A trustworthy privacy policy should describe local storage, outgoing requests, retention, and user controls separately.

Challenge 9: Account verification was the final Chrome blocker

After completing the technical and privacy fields, two issues remained: a publisher contact email had to be provided and verified.

This was a surprisingly ordinary blocker after all the engineering work. Once the contact email was verified and the draft saved, the extension could be submitted for review.

My experience made me add publisher-account readiness to the release checklist. A perfect ZIP is not enough if required account information is incomplete.

Challenge 10: Publication and browser trust are different things

The Chrome extension was successfully published, and I received a confirmation email. Then I tested installation and encountered a new message:

This extension is not trusted by Enhanced Safe Browsing.

Seeing that after publication was confusing at first. But Chrome Web Store publication and Enhanced Safe Browsing's trust assessment are separate processes. Google explains that new developers generally need a period of policy compliance before their extensions are considered trusted by Enhanced Safe Browsing; the message alone is not proof of malware.

I did not remove security features or change extension behavior just to make the warning disappear. The right approach is to keep the extension compliant, be transparent with users, and avoid claiming that approval guarantees the absence of every security or compatibility issue.

Google explains this in its Chrome extension installation guidance.

My practical checklist for publishing a browser extension

If I were releasing another extension tomorrow, I would work through these steps before opening either submission form:

  1. Produce separate, reproducible browser packages. Check which manifest becomes manifest.json in each ZIP.
  2. Inspect the ZIP itself. Confirm that every referenced script, asset, and bundled dependency exists.
  3. Run validation and actual browser tests. Treat warnings and blocking errors differently, but investigate both.
  4. Document third-party libraries. Record exact versions, licenses, and source links; prepare unminified source and build steps if required.
  5. Audit permissions and data flows. Know why each permission exists and what is stored, accessed, or sent over the network.
  6. Prepare public support and privacy URLs. A private code repository is compatible with a public support repository.
  7. Write one accurate product story. Keep the store title, description, screenshots, and capabilities aligned with the shipped build.
  8. Verify the developer account. Contact email, required declarations, and other publisher settings can block submission independently of the code.
  9. Test installation from the public store. Approval does not replace testing what new users actually see.
  10. Maintain the release. Website changes, browser updates, and store policies can affect a working extension later.

What publishing taught me

Building Video Player & Reels Controls for Facebook™ taught me about streaming media and browser APIs. Publishing it taught me something different: shipping software means making its behavior understandable and accountable to people who weren't involved in writing it.

A permission needs a reason. A privacy statement needs evidence. A screenshot needs to match reality. A build needs to be reproducible. And a published product still needs support.

I am glad I went through both processes. The extension is now available on the Chrome Web Store and Firefox Add-ons.

If you're building your first browser extension, my biggest suggestion is simple: start planning the store submission while you're still writing the code. It will save you from having to discover every compliance requirement at the very end.

For more about the extension itself, including its streaming architecture and media-processing challenges, read how I built the Facebook video and Reels player extension. You can also report bugs or suggest improvements in the public support repository.

Note: This article documents my publishing experience in October 2026. Store requirements and browser policies can change; always check current official documentation. Facebook™ is a trademark of Meta Platforms, Inc. This is an independent project and is not affiliated with or endorsed by Meta.