Publishing & updating on the marketplace
Package a registrar driver as a signed marketplace extension, get it approved, and ship updates that never lose a domain or re-charge the operator.
On this page
A finished registrar extension is two things — a driver class and a salieno.json manifest — but a folder on a disk is not a distributable extension. Salieno Core only loads registrars that arrive through marketplace.salieno.com, signed. This article walks the whole lifecycle: how you package and publish a registrar, how an operator installs it, and how you ship updates without ever losing a domain or charging a buyer twice.
Why marketplace-only#
There is no local upload path, and no "drop a folder under storage/" escape hatch. That is deliberate. Every time Core resolves your driver to a live instance, it checks three things, and refuses unless all of them hold:
- Registered — an enabled
registrar_extensionsrow exists for the slug. A hand-copied folder has no row. - Genuine — the stored signed artifact re-verifies against Core's pinned Ed25519 marketplace key, and each on-disk class file's SHA-256 still matches the hash recorded from that verified artifact at install. You cannot forge the signature, and you cannot edit the code after install.
- Entitled — the activated licence still owns the registrar, as decided by the marketplace.
Publishing is how your code earns that signature and how a buyer earns that entitlement. The mechanics of the three gates are covered in Trust model & security; this article is about the path that produces them.
Before you submit: get the package right#
A registrar package is a zip with the manifest and the driver class at the zip root — salieno.json and YourRegistrar.php sitting side by side, plus any helper classes under your namespace. There are no views; Core renders every screen. One level of nesting is tolerated — if the zip wraps everything in a single top-level folder, the installer records that as root_prefix and reads through it — but a flat root is the clean shape. You do not sign anything locally: the marketplace holds the only signing key and signs on approval, so there is nothing on your side to leak.
The marketplace reads your salieno.json at submission, so a few fields carry more weight here than they do while you develop. See The manifest for the full field reference — the ones that matter for publishing:
- `slug` — unique, and it is both the marketplace product slug and the
registrar_slugevery connection profile and registered domain stores. They must match. Choose it once; changing it later means a new product, not an update — and it strands the data keyed to the old slug. - `version` — semver. This is the number the marketplace compares between releases, and it is what an install shows in its Extensions library.
- `entry` — the driver file at the package root, e.g.
"Namecheap.php". Core refuses a package whose declared driver file is not actually present. - `requires_core` — a Core version window, e.g.
">=1.0.0 <2.0.0". This is the range of Salieno Core your registrar supports, not your registrar's own version. It must agree with the core window you enter on the version row (below). - `capabilities` — Core reads the manifest list to gate the admin and client domain screens without loading your driver (cheap, signature-checked). Keep it identical to your driver's
capabilities()method, which is the authoritative runtime source.
Test everything against the real registrar API first — Testing your registrar covers exercising the contract (and the sandbox test_mode toggle) before you hand the code over for review.
Publish: product → version → upload → review → signed#
Publishing runs on marketplace.salieno.com and needs an approved developer account — apply first; you cannot create a product until you are approved. With that in hand:
- Create the product. Set
kindto"registrar", choose a pricing model ("free", or"one_time"withprice_cents), and fill in the name, tagline, and description. Pricing and kind are properties of the product, not of any single release. - Create a version. Enter the
version(semver), the Core window ascore_minandcore_max, and a changelog. The window must agree with the manifest'srequires_core:">=1.0.0 <2.0.0"meanscore_min1.0.0 andcore_max2.0.0. - Upload the artifact — the zip. It is statically scanned, and its manifest is cross-checked against the version row:
kind,version, and the core window must match what you entered. A mismatch, or a structurally unsafe archive (path traversal, an unexpected layout), is rejected on upload. - Submit for review.
- A reviewer approves. On approval the marketplace re-hashes the stored bytes, signs the artifact (Ed25519), and publishes it as the product's live version.
The signing at step 5 is the whole point of the trip: it is what Core's pinned key later verifies, minted at approval time, never on your machine. What exactly gets signed is in the trust note below.
Pricing#
| Model | What the buyer pays |
|---|---|
free | Nothing. A $0 "Get" grants the entitlement — Namecheap ships this way, skipping checkout entirely. |
one_time | A single paid one-off through Stripe checkout. |
There is no subscription and no per-version charge. Free or paid, the operator must own it — hold an active entitlement — for the registrar to appear in their Extensions library and install. The entitlement covers every version of the product, the rule that makes updates free forever, which we come back to below.
How an operator installs#
Installs happen from admin → Domain Registrars → Registrar Extensions. Two things about that screen shape the operator's experience:
- It lists only registrars the licence owns. An operator cannot browse to one they have not acquired and install it anyway; ownership is resolved first.
- Install is a signed, single-use path: resolve → grant → download → verify → install.
Walking that path:
- Resolve the product for this licence and confirm ownership with the marketplace.
- Grant — the marketplace issues a single-use, signed grant. It only issues to an owner, which is exactly why a copied folder from someone else's install is inert: the grant, and therefore the entitlement, is tied to the licence.
- Download the signed artifact.
- Verify the download against Core's pinned key before any file is written to disk. Nothing lands on the box until the signature checks out.
- Install — Core extracts the package, validates the manifest, records a per-file SHA-256 hash tree, writes an enabled
registrar_extensionsrow keyed by your slug, and auto-provisions an unconfigured connection profile under Domain Registrars for the operator to fill in and "Test connection".
From the operator's side it is one click; underneath, every step is signed and verified.
Shipping an update#
An update is just a newer release of the same product. The rule is simple: publish a package whose `version` is greater than the currently published one, the same way as the first — new version row, upload, submit, approve, sign. Semver comparison decides "greater," and the marketplace rejects a re-publish that isn't actually newer.
Real example: Namecheap shipped 1.0.0, then 1.0.1 adding a Test-connection probe. Same slug, same entitlement, same buyers — just a greater version taken through review.
What installs see#
You do not push an update to anyone. Installs discover it: the Extensions library shows "Update available" next to the registrar. When the operator clicks Update, Core runs the same signed path — resolve → grant → download → verify → install — against the new version. The signature is verified before anything is written, identical to a first install.
The atomic swap, and why nothing is lost#
Replacing the code is an atomic swap, not an in-place overwrite. Core stages the new build fully, then moves it over the old one; the previous build is kept until the new one is in place, and restored if the swap fails. Only the code changes. The registrar_extensions row is then updated in place — same row, keyed by the same slug, with the new version, driver hashes, and signed artifact recorded.
Two properties fall out of this, and they are the reassurance you give operators:
No data is lost. A registrar is a stateless artifact — pure code. Everything with state lives in Core's database, keyed by your registrar slug: the connection profile, the stored credentials, and every domain you ever registered, transferred, or renewed. An update changes the code behind the slug; it never changes the slug, so all of that stays wired to the new build untouched. This is also why a driver must never write state into its own folder — beyond breaking the per-file hash check, that folder is the one place an update replaces wholesale.
No one is re-charged. The entitlement covers every version of the product. Updating a paid registrar spends the ownership the buyer already has; it never triggers a new charge. That is what lets you ship fixes and features freely — a buyer who paid once for a one_time registrar gets the next version at no cost.
Versioning in practice#
Two version numbers travel with your registrar, and they answer different questions:
| Field | Question it answers | Example |
|---|---|---|
version | Which release of my registrar is this? | 1.0.1 |
requires_core | Which Core versions can run it? | >=1.0.0 <2.0.0 |
Keep three things consistent on every release: the manifest version, the marketplace version row, and requires_core (which must equal the core_min/core_max you enter). Use a single continuous core range — one >= floor and one < ceiling — and never reference a prerelease or hyphen range; the version row expects clean release semver. Bump version on every release; the marketplace enforces that it climbs. Revisit requires_core only when you actually adopt or drop compatibility with a Core version.
Keep the manifest's informational capabilities list honest as you evolve — if a new version starts backing dns or epp_code, add it to both capabilities() and the manifest so the listing and the domain screens agree.
What the signature covers#
Core verifies two signatures against its pinned marketplace public key before a byte is installed, both produced server-side by the pinned key id `rk-b4425f5d`:
- The artifact signature is over
"salieno.marketplace.artifact/1\0"concatenated withsha256(zip)— it binds the exact bytes you uploaded. - The descriptor signature is over the canonical descriptor JSON (schema
"salieno.marketplace.version/1") — it binds the version metadata (slug, version, core window) to those bytes.
You never hold the key, and Core trusts neither the manifest nor the descriptor unless both signatures verify. The full three-gate model — genuine, registered, entitled — is in Trust model & security.
The lifecycle end to end#
- Build the driver + manifest; test against the real registrar API.
- Zip the package with
salieno.jsonand the driver at the root. - On marketplace.salieno.com (approved developer account): create the product (
registrar,freeorone_time), create a version (semver + core window + changelog), upload the zip, submit. - A reviewer approves; the marketplace signs artifact + descriptor and publishes.
- Owners install from admin → Domain Registrars → Registrar Extensions via the signed resolve → grant → download → verify → install path — which auto-provisions a connection profile.
- To improve it, publish a greater semver; installs see an "Update available" badge.
- The operator clicks Update; the same signed path performs an atomic swap — new code, same slug, no data loss, no re-charge.
New to the series? Start with the Registrar extensions overview.