Skip to content

Publishing & updating on the marketplace

How to publish a Salieno theme on the marketplace and ship updates that verify by signature and never lose operator customisation.

9 min readUpdated Aug 13, 2026
On this page

Publishing & updating on the marketplace#

A local theme becomes an installable product on marketplace.salieno.com through one path only: an approved developer packages a signed version, it passes an automated scan and a human review, and the marketplace signs it server-side with an Ed25519 key that every Core install pins. That signature is the whole trust root. Core deleted operator-side theme upload on purpose — an install will only unpack bytes whose signature was made by a key it shipped with, not bytes an operator ticked a box to trust. This article covers getting listed, packaging, the publish pipeline, and shipping updates that never lose an operator's customisation.

Before you get here your theme must actually be complete — every public page, error page, and auth page — because the fallback finder will silently paper over anything you forgot with a neutral holding page. Run the completeness checklist first.

Become an approved developer#

Marketplace publishing is gated behind an approved developer account. You apply on marketplace.salieno.com, and a person reviews the application before you can create products. This gate is deliberate: because installs trust a signature and not a review checkbox, the marketplace only signs work from developers it has vetted. Approval is what lets every install treat a downloaded theme as safe to unpack.

Once approved you can:

  1. Create a product — the listing itself: name, slug, description, category (theme), preview imagery, and pricing. The product slug matters at install time: it becomes the theme's directory name (resources/views/themes/{slug}/), so choose it as carefully as any package name.
  2. Create versions under that product. The product is the shelf; a version is the artifact people actually download.

Package the theme#

An artifact is a plain zip of your theme directory. The marketplace finds your theme by its packaging manifest, salieno.json, which must sit at the zip root or exactly one level down — the installer strips at most a single wrapping folder. Both layouts are accepted:

bash
# Root layout — salieno.json is the first entry
cd resources/views/themes/aurora
zip -r ../../../../aurora-1.0.0.zip . -x '.git/*' '.DS_Store'

# One-level layout — everything under a single top folder
zip -r aurora-1.0.0.zip aurora -x 'aurora/.git/*'

salieno.json is the marketplace packaging manifest (distinct from theme.json, the runtime manifest Core reads — see Theme anatomy & the two manifests). A theme's salieno.json from the reference theme:

json
{
  "kind": "theme",
  "theme_target": "storefront",
  "name": "Aurora",
  "version": "1.0.0",
  "requires_core": ">=1.0.0 <2.0.0",
  "entry": "layouts/app.blade.php",
  "description": "A fast, accessible hosting and domains front end.",
  "author": "Your Name",
  "license": "proprietary"
}

Every field earns its place, and two of them must be honest:

FieldMeaning
kind"theme" — tells the marketplace this is a storefront theme, not an extension.
theme_target"storefront" — where the theme applies. Do not claim a target you don't fully implement.
versionSemver. Must match the version on the marketplace version row and the version in theme.json.
requires_coreA semver range of the Core versions the theme actually runs on. Declaring >=1.0.0 when you use a 1.4 helper is a lie that ships broken pages.
entryThe layout the theme boots from — layouts/app.blade.php.

Package your precompiled assets, never a build config. The install-time asset pipeline copies assets/ through a strict allowlist and rejects standalone .php files, dotfiles, and symlinks — a theme that relies on @vite or a Node build at runtime will not work once installed.

The publish pipeline#

Creating and publishing a version follows a fixed sequence on the marketplace:

  1. Create a version. Its version must be a valid semver that is strictly greater than the currently published version for that product. The marketplace refuses a version equal to or lower than what's already live — there is no overwriting a published build, only moving forward.
  2. Upload the zip. The version's version field, the version inside salieno.json, and the version inside theme.json must all agree. A mismatch is rejected before review.
  3. Automated safety scan. The marketplace unpacks the artifact and applies the same structural rules the installer will (ThemeService::validateExtractedTheme): the required files must be present — layouts/app.blade.php, sections.json, and a theme.json carrying at least name and version — the JSON must parse, and there must be no standalone `.php` files (only .blade.php is allowed as a view). Zip-slip paths, symlinks, and dangerous extensions are refused here.
  4. Human review. A reviewer looks at what a signature can never attest — that the theme does what the listing claims and isn't hostile. A signature says who built an archive; it says nothing about what unpacking it would do, so a person still judges the contents.
  5. Server-side Ed25519 signing. On approval the marketplace signs the artifact with its private signing key. Two detached signatures are produced (App\Services\Marketplace\MarketplaceSignature): one over the artifact ("salieno.marketplace.artifact/1\0" || sha256(zip)) answering "are these the approved bytes", and one over the canonical descriptor answering "and is this what it claims to be". A binding check ties the two together so a valid artifact signature can't be paired with a different descriptor.
  6. Publish. The version goes live and installs can see it.

Core holds up its end at install time. MarketplaceClient::download() verifies both signatures against a pinned public key — resolved by key id from keys the install shipped with, never taken from the server's response — before a single byte reaches the installer. If sodium is missing, or the digest doesn't match the header, or either signature fails, the download is rejected and nothing is unpacked. That is why Core can safely install a theme it did not build: the trust decision was made by cryptography, not by an operator.

Install-time swap — what actually lands#

Once bytes verify, App\Services\Marketplace\ThemeInstaller::install() does the filesystem work, and it is careful:

  • It extracts to a staging directory under storage/app/marketplace/, never over the live theme — a half-unzipped active theme would serve broken pages for as long as the unzip takes.
  • It re-runs validateInstalled() on the staged files (a signed archive is not exempt from needing a layout and a section catalogue) and re-resolves every entry to refuse any path that escapes the theme's own directory.
  • It swaps atomically by rename. On an upgrade the existing directory is moved aside to {name}.replaced-{time} first, the staged directory is renamed into place, and only then is the old one deleted. If the rename fails, the old directory is put back — the window in which no theme directory exists is one rename long, not one recursive delete long.
  • Finally it publishes assets (publishAssets) and clears the theme caches (forgetTemplateCaches).

Shipping an update#

An update is not a lesser operation than a first install — it runs the identical resolve → verify → swap path. To ship one:

  1. Make your changes locally and re-test against the checklist.
  2. Bump the version in both manifeststheme.json and salieno.json — to the same new semver.
  3. Repackage and create a new marketplace version, strictly greater than the published one. It goes through the same scan, review, and signing.

Choose the bump by what changed:

BumpWhenExample
patch (1.2.0 → 1.2.1)Bug fixes, copy tweaks, asset fixes. No structural change.Fix a broken link in the footer section.
minor (1.2.0 → 1.3.0)New sections, new pages, backward-compatible additions.Add an optional testimonials section.
major (1.2.0 → 2.0.0)Breaking changes — a renamed or removed section slug, a changed content field an operator may have edited.Rename hero to masthead.

On the install side, an operator (or the scheduled update check) sees the newer build and applies it through ThemeIndex::updateTheme(). It downloads, verifies the signature again, and hands the verified artifact to the same installer — so the theme's files are atomically swapped exactly as on a fresh install.

Updates preserve customisation#

This is the guarantee that makes updating safe. Admin page-builder content lives in the database, keyed by theme name — not in your theme's files. When an operator edits a section's copy through the CMS, that edit is stored in Core's tables against the active theme's name. An update swaps only the theme directory; it never runs a migration or touches those rows. So a theme update cannot wipe an operator's customisation — their edited hero copy, their pricing text, their contact details all survive the file swap untouched, and your new blade files simply render against the content that's already there. (This is also why section content reads through getContent(...) with seed defaults as a fallback: unedited fields fall back to your new defaults, edited fields keep the operator's value.)

Because content is keyed by theme name, keep your product slug stable across versions. Changing it makes the update look like a different theme and orphans the stored content.

The safety net: pre-update snapshot and rollback#

Even though the swap is content-safe by construction, updateTheme() takes a belt-and-braces precaution: immediately before the swap it calls ThemeService::snapshotThemeContent($slug), which writes the theme's current page-builder content to a JSON file under storage/app/theme-content-snapshots/ and keeps the newest 10 snapshots per theme. A failure to snapshot is logged but never blocks the update — it is only ever a net.

If a future build regresses or a section-schema change interacts badly with stored content, ThemeService::restoreThemeContent($snapshotPath) replays a snapshot back into the theme's content rows inside a single transaction. And because the installer moves the previous directory aside rather than deleting it up front, the file level has its own recovery: a swap that fails halfway leaves the site with a working theme, not an empty directory.

The distribution gate, in one line#

Approval decides who can publish; the automated scan and human review decide what is fit to publish; server-side Ed25519 signing against a pinned key decides what an install will trust. Together they are what let every Salieno install download and run a third-party theme without ever asking the operator to vouch for code they can't read.

Next: Design & quality guidelines — the look that reads as a $10B product, not something generated.

marketplacepublishingupdatessigningversioning
Was this article helpful?
Still stuck?Contact support
Publishing & updating on the marketplace · Salieno Docs