Skip to content

How Salieno panel extensions work

The mental model for panel extensions: a code-only driver that teaches Core to talk to a control panel, lit up by the capabilities you declare and delivered through the signed marketplace.

8 min readUpdated Aug 15, 2026
On this page

A panel extension teaches Salieno Core to talk to a hosting control panel — cPanel/WHM, Plesk, DirectAdmin, or anything else with an API. This is the front-door article for the series: read it to build the mental model, then follow the links at the end into the parts you need.

Code, not screens#

A panel extension is code only. It is two files: one PHP driver class and a salieno.json manifest.

code
your-panel/
  salieno.json      # the manifest
  YourPanel.php     # the driver class (+ any helper classes under your namespace)

There are no views, no templates, no CSS, and nothing to style. Core owns every screen — the server-group picker, the admin service actions, the client hosting page, the product package selector — and renders them generically from what your driver returns and declares. You never touch a Blade file or a stylesheet, and you never edit Core.

That is the whole reason the model works: because Core does the rendering, a brand-new panel plugs in and lights up everywhere it applies without a single change to Core. Your job is to implement the operations your panel supports and return the exact shapes Core expects. Core does the rest.

The easiest way to write the driver is to extend App\HostingModule\Server\AbstractPanel. It ships safe "not supported" defaults for every method plus a trait of helpers for talking to a panel API, so you override only the methods you actually implement. The one thing you must always add is capabilities(). The driver contract covers the method shapes in detail.

Where a panel shows up#

Once a panel is installed and a server group is pointed at it, the same driver drives four different surfaces:

  • The server-group panel picker — an operator picks your panel by its slug/name when creating a server group, and the Server form renders connection fields (host, port, token or password) straight from your manifest's connection block.
  • Admin service actions — Create, Suspend, Unsuspend, Terminate, Change Package, Change Password, Sync Info, login, To Reseller, and the Account Summary card, on each provisioned service.
  • The client hosting page — SSO into the control panel, disk and bandwidth usage meters, and a Quick Shortcuts card of per-feature links.
  • The product package selector — the plan dropdown shown when an operator creates or edits a hosting product.

Every one of those surfaces is drawn by Core. You supply data and behaviour; Core supplies pixels.

Capabilities: you declare what you do#

Not every panel does everything. Plesk has no cPanel-style "convert an account to a reseller" operation; a minimal panel might not expose usage stats at all. So Core never assumes — it asks.

Your driver's capabilities() method returns a subset of the known capability keys. Before Core renders any control, it checks whether your panel declared the matching capability. Declare it and the control appears; omit it and the control is hidden. A button is never shown-and-failing — if it is on screen, your panel backs it.

php
namespace Salieno\Panel\Plesk;

use App\HostingModule\Server\AbstractPanel;
use App\HostingModule\Server\PanelCapability;

class Plesk extends AbstractPanel
{
    public function capabilities(): array
    {
        return [
            PanelCapability::CREATE, PanelCapability::SUSPEND, PanelCapability::UNSUSPEND,
            PanelCapability::TERMINATE, PanelCapability::CHANGE_PACKAGE, PanelCapability::CHANGE_PASSWORD,
            PanelCapability::ACCOUNT_SUMMARY, PanelCapability::PACKAGES, PanelCapability::SYNC,
            PanelCapability::LOGIN_ACCOUNT, PanelCapability::LOGIN_SERVER,
            // omit RESELLER / QUICK_ACTIONS -> Core hides those controls for your panel
        ];
    }

    // ...override only the methods you support...
}

Extending AbstractPanel declares nothing by default — every control stays hidden until you opt in. That is deliberate: a half-built panel is safe, not broken. Here is what each capability lights up:

CapabilityLights up
createprovisioning on a paid order + admin "Create"
suspend / unsuspend / terminateadmin + status-driven lifecycle + cron suspend/terminate
change_packagepackage change on upgrade/downgrade + admin "Change Package"
change_passwordadmin + client "Change Password"
account_summaryclient usage meters (disk/bandwidth) + admin "Account Summary" card
packagesthe package dropdown in product create/edit
syncadmin/client "Sync Info"
login_accountclient SSO "Login to control panel" + admin login
login_serverserver-level login + the ServerForm "Test connection" probe
quick_actionsthe client "Quick Shortcuts" card
reselleradmin "To Reseller"

A crucial detail: the capabilities array in salieno.json is informational — it lets the marketplace and UI describe what your panel does. The authoritative source at runtime is the driver's capabilities() method. Keep the two in sync, but know that Core trusts the code. The capabilities guide goes surface by surface; honest capabilities covers why declaring a capability you can't truly back is the wrong move.

What the manifest carries#

The manifest is small but load-bearing. Its slug is the identity that ties everything together: it is both what a server group stores and the marketplace product slug, and the two must match. Beyond identity (name, version, namespace, driver, entry, requires_core), it declares two things that let a panel onboard with no core edits:

  • connection supplies the Server form's fields — label, port, auth mode, and the username/password/token labels and help text for your panel.
  • credentials gives Core the rules it needs to pre-generate account credentials that your panel will accept (for example, username_max_length).

The full field reference lives in the manifest article, and the connection block has its own deep dive in connection & credentials.

Distribution: marketplace-only, signed#

Panels are distributed only through marketplace.salieno.com, and only in signed form. There is no local upload path. A folder copied onto a server will not run — not because of a config flag you could flip, but because of how Core resolves a driver.

Installed panel code lives at storage/app/panels/{slug}/, a runtime artifact that is gitignored and deliberately off the normal Composer autoload path. Dropping a folder there loads nothing. Every time Core resolves a driver, App\Services\Panels\PanelRegistry runs three gates in order:

  1. Registered — there is an enabled panel_extensions row for the slug. A hand-copied folder has no row, so it never gets this far.
  2. Genuine — the stored signed artifact re-verifies against Core's pinned Ed25519 marketplace key, and each on-disk class file's SHA-256 matches the hash recorded from that verified artifact at install time. You cannot forge the signature, and you cannot edit the code after install without breaking the hash check.
  3. Entitled — the activated licence still owns this panel, as decided by the marketplace (not by anything on the box). This is what makes "a copied folder won't run" true even when the folder is genuine: the same signed code copied onto an install that never bought the panel fails here. The check is cached briefly; if the marketplace is unreachable, Core falls back to the last confirmation within a grace window, then fails closed.

Only after all three gates pass does Core unlock the slug for a scoped autoloader that requires only that extension's hash-matching files. Nothing else can trigger the load. The practical consequence for you as an author: never write state into your panel's own folder — it would break the per-file hash check. Keep all state in what Core passes your methods and in the panel server itself. The security & trust model explains each gate in full.

Publishing follows from this. You submit the package; a reviewer approves it; on approval the marketplace signs the artifact with its key. Pricing is set at creation — free, or a paid one-time purchase — and the entitlement a buyer gets covers every version of your product. Operators install from the admin Extensions library, which lists only the panels their licence owns. See publishing & updates for the submission flow.

Because a panel is a stateless artifact, updates are painless. You publish a higher semver; installs see an "update available" badge and can apply it with one click via the same signed install path, an atomic code swap. No data is lost — server groups, servers, and every provisioned account live in the database keyed by the panel slug, which an update never changes. And since the entitlement covers all versions, updating a paid panel never charges again.

Where to go next#

The rest of the series builds on this model:

  • [Quickstart](/panel-development/panel-quickstart) — scaffold a working driver and get it resolving.
  • [The manifest](/panel-development/panel-manifest) — every salieno.json field, explained.
  • [The driver contract](/panel-development/panel-contract) — each method's arguments and exact return shape.
  • [Capabilities](/panel-development/panel-capabilities) — the capability system in depth, surface by surface.
  • [Connection & credentials](/panel-development/panel-connection) — the Server form and account-credential rules your manifest drives.
  • [Testing](/panel-development/panel-testing) — how to exercise a driver against a real panel before you ship.
  • [Publishing & updates](/panel-development/panel-publish) — submit, get signed, release new versions.
  • [Security & trust](/panel-development/panel-security) — the three gates in full, and what they mean for how you write code.

Two worked drivers ship in the repo and are worth reading alongside these articles: cPanel/WHM (full-featured, declares all thirteen capabilities) and Plesk (a subset panel that honestly omits reseller and quick actions). Start with the quickstart when you are ready to write code.

panelsarchitectureoverview
Was this article helpful?
Still stuck?Contact support
How Salieno panel extensions work