Developers

Store, seal and updates

Store extensions are signed by Mozilla and sealed by Pulsar, update by themselves, and can be installed from a button on your own site. You can also ship outside the store.

Where extensions come from

SourceChecksWhat people see
The Pulsar StoreMozilla signature and Pulsar sealThe install sheet with “Reviewed by Pulsar”
Your website or a fileMozilla signature (your own, unlisted)The “from outside the store” sheet, following the user’s Ask / Allow / Block setting
A Lite extensionNone needed (page scripts only)The same outside-store sheet
Chrome, Mozilla or Microsoft stores—Refused: Pulsar only installs extensions made for Pulsar

The Pulsar seal

After review, Pulsar has your file signed by Mozilla (unlisted, never listed on Mozilla’s store) and adds its seal: an Ed25519 signature over the exact signed file.

Seal
message = "pulsar-seal-v1:" + lowercase hex sha256 of the signed .xpi
seal    = base64( Ed25519 signature of message )
  • Pulsar checks the seal over the bytes it is about to install. A changed file has no valid seal.
  • A file with a valid seal counts as a store extension wherever it was downloaded from (a mirror, a file).
  • You never handle the seal. Upload your zip; the store does the rest.

Updates from the store

  • Upload a new version with a higher version. It is reviewed like the first one.
  • Pulsar checks for updates daily (and on “Check for updates”), checks the file’s hash and seal, and installs it.
  • If the new version asks for permissions the old one didn’t have, people decide first. Otherwise it updates silently.
  • Your specs: send them again in Pulsar.onConnect. Values that no longer fit are dropped.

Shipping outside the store

Sign through your own Mozilla account (unlisted) and host the .xpi on your site over https. Add an update_url so people can get updates:

manifest.json
"browser_specific_settings": { "gecko": {
  "id": "pricewatch@dealab.example",
  "update_url": "https://dealab.example/pulsar/updates.json"
} }
https://dealab.example/pulsar/updates.json
{ "addons": { "pricewatch@dealab.example": { "updates": [
  { "version": "1.3.0",
    "update_link": "https://dealab.example/pulsar/price-watch-1.3.0.xpi",
    "update_hash": "sha256:…" }
] } } }
  • Updates only come when the user turned on Allow updates from dealab.example at install, only through your update_url, only from that host (or its subdomains), only over https.
  • update_hash is checked when present. Always add it.
  • A file from outside the store can never replace a store extension with the same id.

An install button on your site

Your extension is on the store

Link to its store page. In Pulsar it opens with Add to Pulsar; in other browsers it shows “Get Pulsar to install”.

html
<a href="https://plugins.pulsarbrowser.com/p/price-watch">Add to Pulsar</a>

Only on your site

Link straight to your signed .xpi over https. Serve it as application/x-xpinstall (or keep the .xpi ending). Pulsar downloads it, checks it and shows the outside-store sheet with “Allow updates from your site”.

html
<a href="https://you.example/pulsar/tool-1.2.xpi">Install in Pulsar</a>
Never link to the Chrome or Mozilla stores: Pulsar refuses those.

QR codes

Put the store link (or pulsar://plugin/<your gecko id>) in a QR code. Pulsar’s scanner (Extensions › + › Scan a QR code, or the launcher’s Scan QR) shows what it found:

CodePeople seeButtons
https://plugins.pulsarbrowser.com/p/<slug> or pulsar://plugin/<id>Extension link found · nameCopy link · View extension
An https link ending in .xpi or .pulsar.zipExtension file found · file nameCopy link · Install
Any other linkLink foundCopy · Open

window.pulsarStore

Pulsar’s store pages talk to the browser through window.pulsarStore. It exists only on the Pulsar Store’s own pages (https, top frame), never on other sites, so your site should link to your store page as above. This is what the store page uses:

window.pulsarStore
interface PulsarStore {
  version: 1; isPulsar: true; appVersion: string;
  getInstalled(): Promise<{ id: string; version: string; enabled: boolean }[]>;
  install(id: string): Promise<{ result: 'installed' | 'cancelled' | 'failed'; reason?: string }>;
  uninstall(id: string): Promise<{ result: 'removed' | 'cancelled' | 'failed'; reason?: string }>;
  update(id: string): Promise<{ result: 'updated' | 'cancelled' | 'failed' | 'none'; reason?: string }>;
  setEnabled(id: string, enabled: boolean): Promise<{ ok: boolean }>;
  open?(id: string): Promise<{ ok: boolean }>;
  onChanged(cb: (e: { type: 'installed' | 'removed' | 'enabled' | 'disabled' | 'updated';
                      id: string; version?: string; enabled?: boolean }) => void): () => void;
}

// ready check
if (window.pulsarStore) init(); else addEventListener("pulsarstoreready", init);
  • id is the gecko id from your manifest, not the store slug.
  • Nothing installs by itself: install always shows Pulsar’s own sheet, and needs a tap from the user.
  • One install at a time per tab; at most 30 calls per 10 seconds.