The salieno.json manifest
Every field in a registrar's salieno.json manifest, key by key — the schema Core validates, the capabilities that gate the UI, and the credentials that render the config form.
On this page
Every registrar extension ships two things: a driver class and a salieno.json manifest that sits next to it. The manifest is how Core learns your registrar exists before it ever loads a line of your code — it names the registrar, points at the driver class, states which Core versions it targets, and declares the two things Core acts on generically: the capabilities that gate the UI and the credentials that render the Domain Registrars config form. This article documents every field.
For the driver methods it points at, see The driver contract. For what each capability lights up on screen, see Capabilities and the UI.
A complete manifest#
Here is the real Namecheap manifest, annotated. The comments are for reading only — the file on disk must be strict, comment-free JSON.
{
"schema": "salieno.registrar/1", // manifest format version — always this string
"kind": "registrar", // this is a domain registrar (not a panel or theme)
"slug": "namecheap", // unique id: the profile's registrar_slug AND the marketplace product slug
"name": "Namecheap", // shown in the Extensions library + Domain Registrars
"version": "1.0.1", // YOUR registrar's semver
"namespace": "Salieno\\Registrar\\Namecheap", // your PSR-4 root (trailing backslash optional)
"driver": "Namecheap", // the class in that namespace implementing the contract
"entry": "Namecheap.php", // the driver file, relative — must end in .php
"requires_core": ">=1.0.0 <2.0.0", // the CORE version window you support (not your version)
"author": "Salieno", // optional; marketplace listing metadata
"description": "Register, transfer, renew and manage domains through Namecheap...", // optional blurb
"homepage": "https://www.namecheap.com/support/api/intro/", // optional; vendor API docs
"capabilities": [ // informational; driver capabilities() is authoritative
"register","transfer","renew","nameservers","child_nameservers",
"registrar_lock","id_protection","auto_renew","contacts","sync","transfer_status"
],
"credentials": [ // renders the Domain Registrars config modal, field by field
{ "key": "api_user", "label": "API User", "type": "text", "required": true,
"help": "Your Namecheap API username (usually your account username)." },
{ "key": "api_key", "label": "API Key", "type": "password", "required": true,
"help": "Enable API access and copy the key from Profile > Tools > Namecheap API Access." },
{ "key": "username", "label": "Account Username", "type": "text", "required": false,
"help": "Defaults to the API User when left blank." },
{ "key": "client_ip", "label": "Whitelisted Server IP", "type": "text", "required": true,
"help": "Your server's public outbound IP, whitelisted in Profile > Tools > API Access." }
]
}Top-level fields#
schema#
Always "salieno.registrar/1". Core compares this exactly; anything else is rejected at install with "that registrar uses a manifest format this Salieno version does not understand." It is how a future manifest revision can change shape without breaking installs of the current one.
kind#
Always "registrar". It distinguishes a domain-registrar extension from other marketplace extension kinds (panels, themes). A wrong value is refused as "that extension is not a domain registrar."
slug#
The registrar's unique identifier, lowercase, [a-z0-9] start then [a-z0-9_-]. This one string does double duty, and it is the sharpest edge in the whole manifest:
- It is the marketplace product slug — the id of the listing a buyer is entitled to.
- It is the `registrar_slug` a connection profile stores to remember which driver backs it. Every domain is provisioned and renewed through a profile keyed to this slug.
On install, Core checks that the manifest slug matches the product it was fetched as (compared case-folded); a mismatch fails with "that registrar package does not match the product it was fetched as." Because domains reference the profile that carries this slug, it is also the value an update must never change — changing it would orphan every domain pointing at that registrar. Pick it once, at creation, and leave it.
name#
The human label shown in the admin Extensions library and the Domain Registrars list. Free text — "Namecheap", "ResellerClub".
version#
Your registrar's own semantic version, e.g. "1.0.1". This is the number the marketplace tracks for update badges: to publish an update you increment it, and its semver must be strictly greater than the currently published version. It is stored on the installed row and shown next to your registrar in the library. Do not confuse it with requires_core below.
namespace#
Your PSR-4 root, written as a PHP namespace — "Salieno\\Registrar\\Namecheap". In JSON each backslash is escaped, so a single \ is written \\. The trailing backslash is optional (Namecheap omits it; Core normalises either way). Core maps this namespace onto your package files through a scoped autoloader that is unlocked only after the security gate passes, so your namespace should be distinctive to your registrar. It is validated for a conservative namespace shape at install, failing with "that registrar declares an invalid namespace."
driver#
The class within that namespace that implements the contract — "Namecheap". Core resolves the fully qualified class as namespace + driver, so here Salieno\Registrar\Namecheap\Namecheap. If your driver lives in a sub-namespace you may write "Drivers\\Namecheap", and Core will look for the class at Salieno\Registrar\Namecheap\Drivers\Namecheap. The resolved class must be instanceof App\DomainRegistrars\RegistrarInterface; extending AbstractRegistrar satisfies that automatically (see The driver contract). An invalid value is refused as "that registrar declares an invalid driver class."
entry#
The driver file's path relative to the package root — "Namecheap.php". It must end in `.php`; the marketplace requires it when you submit. It should point at the same file namespace + driver resolves to: strip the namespace root off the fully qualified class, turn \ into /, add .php. So the Namecheap class resolves to Namecheap.php at the package root, and a "Drivers\\Namecheap" driver resolves to Drivers/Namecheap.php. Note that the Core installer does not read entry to find the driver — it derives that path from namespace + driver and checks it exists, failing with "that registrar does not contain the driver it declares." Point entry at that same file so the marketplace and Core agree.
requires_core#
A Core version range, e.g. ">=1.0.0 <2.0.0". This is the single most-misread field: it declares which versions of Salieno Core your registrar is compatible with, not anything about your registrar's own version. It must be one continuous range — no prerelease or hyphen ranges. Use a range that excludes the next major (<2.0.0) so a breaking Core release does not silently run an untested registrar against it.
author, description, homepage#
All optional, all purely informational for the marketplace listing — author is a display credit, description a one-line blurb, homepage a URL (Namecheap points it at the vendor's API docs). Core stores them but does not act on them.
The two validators#
A registrar package is checked twice, and the manifest has to satisfy both.
- The marketplace, when you submit, needs
kind,name,version,requires_coreand an entry pointer. It accepts the entry under any of four aliases —entry,entry_point,entrypointormain— so it can read packages authored to older conventions; useentry. - The Core installer (
RegistrarInstaller,MANIFEST_SCHEMA = 'salieno.registrar/1'), when an operator installs from the Extensions library, needsschema,kind,slug,namespaceanddriver, and then confirms the driver file thatnamespace+driverresolves to actually exists in the package.
The two required sets overlap only on kind. Include every field above and both validators pass; drop one that only the other cares about and the package is rejected at the stage that needs it. The annotated manifest is the union of both — copy its shape.
The capabilities array#
capabilities is the manifest's first big payoff. It is a flat array of capability keys, and Core reads it to gate the UI without loading your driver — the array rides inside the signed, hash-checked manifest, so Core can decide which controls to render on the admin domain page, the client domain manager and checkout without paying to load and run a line of your code.
It is not authoritative at runtime. The moment an operation actually runs, Core trusts your driver's capabilities() method, never this array. Keep the two identical: a capability listed here but missing from the method paints a control that then fails; the reverse hides a control your driver really backs. The manifest gates cheaply; the driver decides for real.
The valid keys are: register, transfer, renew, nameservers, child_nameservers, dns, epp_code, registrar_lock, id_protection, auto_renew, contacts, sync, transfer_status, price_sync. What each one lights up is documented in Capabilities and the UI. Namecheap declares eleven of them and omits dns, epp_code and price_sync — so Core hides registrar-hosted DNS, hides the API "Get EPP" button (the client sees manual-retrieval instructions instead), and hides TLD price import. Never shown-and-failing.
Availability search is not a capability. Core checks whether a domain can be registered itself — RDAP, then WHOIS on port 43, then a DNS probe — before your driver is ever involved, so there is no checkAvailability to declare and search works on a fresh install with no registrar configured.
The credentials list#
credentials is why adding a registrar needs no changes to Core. It is a list (not an object) of field definitions; Core renders the Domain Registrars config modal straight from it, one input per entry, in the order given. Each entry:
| Key | What it does |
|---|---|
key | The field's storage key — what your driver reads back with $this->getParam($registrar, 'api_key'). |
label | The form label shown above the input ("API Key", "Whitelisted Server IP"). |
type | "text" or "password". password masks the input in the form; pick it for secrets. |
required | Boolean. Core blocks saving the profile until every required field is filled. |
help | Inline help under the field — where to find the value, gotchas like IP whitelisting or defaults. |
The values an operator enters are stored on the connection profile (a DomainRegister row) and read back per call inside your driver — never from constructor state, because the registry instantiates the driver once and reuses it for every domain. $this->getParam($registrar, 'api_key', '') returns one stored field.
Do not declare a sandbox or test-mode credential. The registrar's sandbox toggle is provided by Core on every connection profile; read it in your driver with $this->isSandbox($registrar) and point your base URL at the sandbox host accordingly. Adding it to credentials would render a second, redundant control. The full config-form and testConnection walkthrough is in Credentials and the config form.
Packaging#
Zip the manifest and the driver at the root of the archive:
namecheap.zip
salieno.json
Namecheap.phpOne extra level of nesting is tolerated — Core records the wrapper as a root_prefix and strips it on extract — but a flat root is cleanest. Any helper classes go under your namespace next to the driver. You do not sign the package locally: on marketplace approval the signature is added server-side, and Core verifies it against a pinned key at install and again every time the driver loads. The full submit-and-publish flow is in Publishing to the marketplace.
Sharp edges, in one place#
- `slug` is the marketplace product slug and the connection profile's `registrar_slug` at once. Choose it at creation and never change it — an update that changes the slug orphans every domain pointing at that registrar.
- `entry` must end in `.php` and should name the same file
namespace+driverresolves to; install fails if that namespace-derived file is not in the package. - `requires_core` is the Core version window, not your registrar's version. Your version lives in
version, as a single continuous range with no prerelease. - The manifest satisfies two validators — the marketplace (
kind,name,version,requires_core,entry) and the Core installer (schema,kind,slug,namespace,driver, plus the driver file existing). Include all of them. - The manifest `capabilities` array gates the UI cheaply; the driver's `capabilities()` method is the runtime authority. Keep them identical.
- `credentials` is a list, and never carries a test-mode field — the sandbox toggle is Core-provided, read with
$this->isSandbox($registrar).
With the manifest in hand, move on to The driver contract to implement the methods it points at.