Publishing PHPLens to every browser store
One codebase, two packages:
| Package | Built by npm run build |
Used for |
|---|---|---|
dist/phplens-chromium-<version>.zip |
manifest.json as-is (service worker) | Chrome Web Store, Microsoft Edge Add-ons, Opera Add-ons, Brave/Vivaldi (they install from the Chrome Web Store), Safari (converted with Xcode) |
dist/phplens-firefox-<version>.zip |
manifest.json + manifest.firefox.json (event page scripts, gecko id, data-collection declaration) | Firefox Add-ons (AMO) |
The listing copy, permission justifications and asset sizes are in
store/listing.md; the privacy policy is PRIVACY.md.
0. Release checklist (every release)
- Bump the version in both
manifest.jsonandpackage.json(the build refuses to run if they differ). Stores require a version strictly higher than the last published one. - Add an entry to
CHANGELOG.md. - Run the release build:
npm run releaseThis runs the unit tests, regenerates the icons, checks the store limits (name ≤ 45, description ≤ 132 characters) and writes both zips to
dist/. - Smoke-test each package once:
- Chromium:
chrome://extensions→ Developer mode → Load unpacked →dist/chromium. - Firefox:
about:debugging#/runtime/this-firefox→ Load Temporary Add-on →dist/firefox/manifest.json; grant Access your data for all websites under the add-on’s Permissions tab. - Run
npm run demoand openhttp://127.0.0.1:8099/; every state should produce the right badge, dialog and notification.
- Chromium:
- Lint the Firefox package with Mozilla’s tool (needs network access to fetch
web-ext):npm run lint:firefoxWarnings about
minimum_chrome_versionorauthorbeing unknown keys are expected and harmless; errors are not. - Tag the release in git (
git tag v1.1.0) so the published package can be reproduced.
Hosting the privacy policy: Chrome, Edge and Opera want a URL. GitHub Pages is enabled
for this repository (branch main, folder / (root), Jekyll via _config.yml), so use:
https://haniamin90.github.io/phplens/PRIVACY
The rendered file on GitHub, https://github.com/Haniamin90/phplens/blob/main/PRIVACY.md,
works as a fallback. The project page itself is https://haniamin90.github.io/phplens/.
Pushing a tag vX.Y.Z runs .github/workflows/release.yml, which re-runs tests and build
and attaches both zips to a GitHub release with the matching CHANGELOG.md section. Use
those zips for the store uploads so what you publish is exactly what CI built.
1. Chrome Web Store (Chrome, Brave, Vivaldi, Arc, …)
Account: Chrome Web Store Developer Dashboard. One-time registration fee (USD 5). Enable 2-step verification on the Google account.
- New item → upload
dist/phplens-chromium-<version>.zip. - Store listing: name, summary, description and category from
store/listing.md; icon is read from the package; add 1–5 screenshots (1280×800). Optional promo tiles. - Privacy practices:
- Single purpose: paste the statement from
store/listing.md. - Permission justifications: paste each one (
webRequest, host permissions,storage,notifications), state no remote code. - Data usage: tick nothing in the “collects” list, tick all three certifications.
- Privacy policy URL: your hosted
PRIVACY.md.
- Single purpose: paste the statement from
- Distribution: Public (or Unlisted for a soft launch); all regions.
- Submit for review. Reviews for extensions with
<all_urls>+webRequesttypically take a few days. If the reviewer asks why broad host access is needed, answer with the host-permission justification (the extension cannot know in advance which sites run PHP). - After approval, the item gets a permanent URL
https://chromewebstore.google.com/detail/<id>. Note the extension ID; it stays the same across versions.
Updating: Package → Upload new package with a higher version → submit. Users receive the update automatically within hours.
Brave, Vivaldi, Arc, Opera (Chromium) install from the Chrome Web Store; nothing extra is needed for them.
2. Microsoft Edge Add-ons
Account: Partner Center – Microsoft Edge program. Free registration with a Microsoft account.
- Create new extension → upload the same
dist/phplens-chromium-<version>.zip. - Availability: public, all markets.
- Properties: category Developer tools, privacy policy URL, support URL (repository issues page), website.
- Store listing (per language): description, short description (summary), 1–10 screenshots (1280×800 or 640×400), optional promotional tile 300×300, the search terms.
- Notes for certification: write “Passive security check. Reads response headers via
webRequest (observe only). Test with:
npm run demo→ http://127.0.0.1:8099/ (or any PHP site). No login required.” - Publish. Certification usually completes within 1–7 days.
Updating: open the extension → Packages → upload the new zip → Publish.
3. Firefox Add-ons (AMO)
Account: addons.mozilla.org developer hub. Free. Every Firefox extension must be signed by Mozilla even if you distribute it yourself, so AMO is on the path either way.
The add-on ID is permanent once submitted. The Firefox manifest ships
browser_specific_settings.gecko.id = "phplens@haniamin90.github.io" (an email-style id
on a host you control; a GUID also works). If you publish under another account, change it
in manifest.firefox.json before the first submission; it cannot be changed later.
- Submit a New Add-on → On this site (listed) → upload
dist/phplens-firefox-<version>.zip. AMO runs the same linter asnpm run lint:firefox; fix any errors it reports. - Source code: not required (the package is plain, unminified JavaScript). If asked,
upload a zip of the repository without
dist/. - Listing: name, summary, description, categories (Privacy & Security), tags,
support email/site, license (MIT), privacy policy (paste the text of
PRIVACY.md), screenshots (any size, up to 10). - Data collection: the manifest already declares
data_collection_permissions: { required: ["none"] }; confirm “no data collected” in the form. - Submit. Automated review signs most add-ons within minutes; a human review can
follow and may ask about the
<all_urls>host permission andwebRequest. Reply with the justification text. - Firefox treats host permissions as optional in Manifest V3: after installation users may need to allow Access your data for all websites in the add-on’s Permissions tab. Say so in the listing description (already included in the copy).
Updating: Upload New Version on the add-on page with a higher version number.
Self-distribution / enterprise: submit as On your own (unlisted); AMO returns a signed
.xpi you can host anywhere or deploy by policy.
Firefox for Android is included: manifest.firefox.json declares gecko_android
(minimum 142, the first Android release that understands the data-collection key). The
extension needs no desktop-only API; notifications are simply unavailable there and skipped.
Command-line submission (recommended)
Instead of clicking through the upload form, submit with Mozilla’s own web-ext tool and
the listing metadata in store/amo-metadata.json (summary, description, category, tags,
homepage, support URL, licence, release notes and notes for the reviewer):
- Sign in at https://addons.mozilla.org/developers/ and generate API credentials at https://addons.mozilla.org/developers/addon/api/key/ (a JWT issuer and secret).
- In a terminal, from the project folder, with the credentials in environment variables
that
web-extreads automatically (never commit them):npm run build WEB_EXT_API_KEY="user:12345:67" WEB_EXT_API_SECRET="…" npm run sign:firefoxPowerShell:
$env:WEB_EXT_API_KEY="user:12345:67"; $env:WEB_EXT_API_SECRET="…"; npm run sign:firefox web-extuploadsdist/firefox, waits for validation, creates the listed add-on with the metadata, and prints the AMO URL. A listed add-on is distributed by AMO, so no signed file is downloaded; the version enters the review queue.- Finish on the AMO developer hub: upload screenshots (Edit Listing → Images) and check
the Privacy policy field (paste
PRIVACY.md); the submission API does not take either.
The first run of web-ext sign creates the add-on (the id comes from the manifest); later
runs with a higher version submit updates to it. The metadata file mirrors the body of
AMO’s add-on create API call: listing fields (categories, summary, description,
slug, tags, …) sit at the top level next to a version object. A “field is required”
error from the server means a field is missing or nested in the wrong place. Keep store/amo-metadata.json in sync with
CHANGELOG.md for the release notes.
4. Opera Add-ons
Account: Opera Add-ons developer dashboard. Free.
- Upload Addon → upload
dist/phplens-chromium-<version>.zip. - Fill the listing (same copy), category Privacy & Security, screenshots, privacy policy URL.
- Submit for moderation (typically 1–2 weeks).
Most Opera users can also install directly from the Chrome Web Store, so this step is optional.
5. Safari (macOS, iOS, iPadOS)
Safari does not install web-store zips. The extension must be wrapped in a native app with Xcode and distributed through the App Store (or notarized for direct download on macOS).
Requirements: a Mac with Xcode 15+, an Apple Developer Program membership (USD 99/year) for App Store distribution.
- Build the Chromium package, then convert it:
npm run build xcrun safari-web-extension-converter dist/chromium \ --project-location ./safari \ --app-name "PHPLens" \ --bundle-identifier com.example.phplens \ --macos-only # drop this flag to also generate the iOS targetThe converter prints warnings for unsupported manifest keys (
minimum_chrome_version,webRequest); they are expected. - Open
safari/PHPLens/PHPLens.xcodeproj, set your Team under Signing & Capabilities for both the app and the extension targets, and run it once. In Safari enable Develop → Allow Unsigned Extensions during development, then turn the extension on under Safari → Settings → Extensions and allow it for All Websites. - Behaviour on Safari: there is no
webRequest, so header observation is unavailable andbrowser.notificationsdoes not exist. PHPLens relies on page hints and the active fallback (a HEAD request to the page) to read the version, and alerts through the badge and the in-page dialog. Keep Active fallback on for Safari users. - App Store: in Xcode, Product → Archive → Distribute App → App Store Connect. In App Store Connect create the app record, add screenshots of the Safari extension in use (macOS: 1280×800 or larger; iOS: device sizes), the description, the privacy policy URL, and fill the App Privacy questionnaire with “Data Not Collected”. Submit for review (usually 1–3 days). Mention in the review notes that the app is a Safari web extension container and how to test it (demo server URL, or any PHP site).
- Outside the App Store (macOS only): Distribute App → Developer ID, notarize, and
ship the
.app; users install the extension by launching the app once.
6. Keeping the release table current
The advisory facts (fixed versions 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12) are bundled in
src/lib/php-check.js. The latest release per branch refreshes daily from php.net, so a
new PHP point release does not require a store update. A new advisory does: add it to
VERSION_TABLE.advisories, bump the version, and publish through all five channels above.
7. Store review answers you will probably need
- Why do you need access to all websites? The extension cannot know in advance which
sites run PHP; it reads only the listed headers and page hints and never modifies
requests. See
PRIVACY.md. - Does the extension send requests to websites? Only the optional HEAD request for the page the user is already on, when no version was observed; it can be disabled in settings. Everything else is read-only observation of responses the browser already received.
- Does it execute remote code? No. The daily php.net request returns version numbers as JSON data only.
- How do we test it? Run
node tools/demo-server.mjsfrom the source, openhttp://127.0.0.1:8099/, visit each demo state; or open any site that advertisesX-Powered-By: PHP/….