How Salieno themes work
The mental model for Salieno Core themes: Core ships themeless, a theme owns every public page, and the resolution engine ties them together.
On this page
- How Salieno themes work
- Core ships themeless, and privileges no theme
- A theme is a directory
- The theme owns presentation; Core owns everything else
- How a page becomes HTML: the resolution engine
- What happens when a view is missing
- Two manifests, one version
- Distribution: signed artifacts, and updates that keep customisation
- The road ahead
How Salieno themes work#
Before you write a single Blade file, it helps to hold the right mental model. This guide teaches you to build a complete Salieno Core storefront theme from an empty directory. This first article gives you the architecture that everything else rests on: what Core owns, what a theme owns, how a page becomes HTML, and what happens when a view is missing or no theme is installed at all.
Core ships themeless, and privileges no theme#
There is no built-in default theme, and no theme is special. Salieno Core ships with an empty resources/views/themes/ directory. The active theme is purely the operator's stored choice, resolved in exactly one place — App\Services\Helpers\ThemeHelper::activeThemeName():
public function activeThemeName(): string
{
return session('template') ?? app(SettingsHelper::class)->gs('active_theme') ?? '';
}A session preview override wins first (that is how the admin previews a theme without switching the live site), then the stored active_theme setting. When nothing is installed or selected, this returns the empty string — deliberately, so callers can still concatenate a view name without a crash.
The consequence for you as a theme author is the single most important rule in this guide: your theme must be complete. Because Core does not fall back to a bundled theme for a missing view, every public page your theme is expected to serve must be shipped by your theme. There is no safety net theme underneath you.
A theme is a directory#
A theme is not a plugin or a class — it is a directory at resources/views/themes/{name}/ containing three things:
- Blade views — the layouts, pages, sections, and partials that render your storefront.
- Precompiled assets — plain CSS, JS, images, and fonts under
assets/, published to the public docroot at install time. (Uploaded themes ship compiled output; there is no@viteor Tailwind@applyat runtime. See Assets.) - JSON manifests —
theme.json,salieno.json, andsections.json.
The minimum a theme must contain to install and activate is small, but non-negotiable. App\Services\ThemeService treats two files as always-required:
protected array $requiredFiles = [
'layouts/app.blade.php',
'sections.json',
];On top of that, theme.json is validated when present (it must be valid JSON, and at minimum carry a name and version). In practice a real, complete theme ships far more than the minimum — every page, its error views, its auth views — but these are the files the installer refuses to proceed without.
Here is the shape of the reference theme's top level, which you will come to recognise:
themes/salieno/
├── layouts/
│ ├── app.blade.php # the outer shell (required)
│ └── frontend.blade.php
├── sections.json # ordered section list + CMS definitions (required)
├── theme.json # runtime manifest Core reads
├── salieno.json # marketplace packaging manifest
├── home.blade.php pricing.blade.php contact.blade.php …
├── sections/ # one blade per section slug
├── auth/ cart/ domains/ products/ kb/ store/
├── errors/ # 403 404 419 429 500 503
├── partials/
├── components/theme/ # optional x-theme.* overrides
└── assets/ # css/ js/ images/ (precompiled)The Anatomy article walks every file and both manifests in detail.
The theme owns presentation; Core owns everything else#
Keep this line clean in your head, because it governs which files you touch:
| Core owns | Your theme owns |
|---|---|
| Routes, controllers, Livewire components | Every public HTML page |
| Data, models, business rules, cart/checkout logic | Layout, markup, CSS, JS, images |
| Auth, sessions, billing, licensing | The visual look and interaction feel |
| The page-builder CMS backend | The section markup the CMS fills |
Core decides what a page is and what data it has; your theme decides how it looks. You never write routing or query logic in a theme. You render what Core hands you. The critical practical corollary is on interactive pages: Livewire drives cart, checkout, auth, and search, so you may restyle their markup freely as long as you preserve the `wire:model` / `wire:submit` / `wire:click` bindings. Strip a binding and the page stops working. This is covered concretely in Auth pages and Pages.
How a page becomes HTML: the resolution engine#
Every theme lookup goes through a small set of helpers in app/Helpers/helpers.php, which delegate to ThemeHelper. These four are the ones you will use constantly:
| Helper | Returns | Example |
|---|---|---|
activeThemeName() | bare theme name, or '' | salieno |
activeTheme($asset=false) | view prefix, or asset prefix when true | themes.salieno. / assets/themes/salieno/ |
themeViewName($view) | a fully-qualified view name | themeViewName('auth.login') → themes.salieno.auth.login |
themeAsset($path) | public URL with an ?v=<mtime> cache-buster | themeAsset('css/theme.css') |
Controllers render pages by name through these. FrontendController::themeView() is the canonical path:
public function themeView($view, $data = []): View
{
return view(themeViewName($view), $data);
}So a request for the home page becomes view('themes.salieno.home'). Auth pages, being Livewire components, resolve both their view and their layout from the theme — return view(themeViewName('auth.login'))->layout(themeViewName('layouts.app')) — which is exactly why a theme that omits its auth views breaks login. Error pages resolve activeTheme().'errors.'.$code from bootstrap/app.php.
Canonical names. Use thetheme*helpers (themeViewName,themeAsset,activeTheme,themeMeta). The oldertemplate*aliases (templateViewName,templateAsset,activeTemplate) still work for themes shipped before the rename, but they are deprecated — write new themes with thetheme*names. Full list in the Helper reference.
What happens when a view is missing#
This is where the "themeless" design shows its teeth, and the behaviour is deliberately split into two cases by App\View\ThemeFallbackViewFinder:
- No theme installed at all. The themes directory is empty, so
activeThemeName()is''and every lookup resolves tothemes..{view}. Rather than throw, the finder catches this narrow case and renders a neutral Core holding page (thetheme-missingview, which lives outside the themes tree so it renders even when the tree is empty). This is not an error path — it is the first-run path, the screen an operator sees before installing any theme.
- A theme is installed but is missing a view. This still throws "View not found." That is intentional: a missing partial is a bug its author needs to see, and substituting the whole "no theme installed" page into a layout slot would silently splice a full-page error into a fragment and call it success.
if (str_starts_with($name, 'themes.') && $this->noThemeInstalled()) {
return parent::find(self::MISSING_THEME_VIEW); // neutral holding page
}
throw $e; // theme installed but view genuinely missing — surface the gapThe one component that does not hard-fail is the shared x-theme.* primitive library: if your theme omits an overridable component, Core renders its own base version from resources/views/components/theme/. That is a fallback for UI primitives, not for pages — the engine never depends on one privileged theme. See Components.
The section renderer is also tolerant by design. partials/render-sections loops the ordered secs array and includes each slug only if the view exists, so a renamed or missing section is skipped rather than fatal — while a missing page is not. Sections explains why the two rules differ.
Two manifests, one version#
A theme carries two manifest files with distinct jobs:
- `theme.json` is the runtime manifest Core reads:
name,version,author,description,preview,requires_salieno, asupportsblock (sections,dark_mode,rtl),settings, and acolorsmap.themeMeta('colors.primary')reads from here. - `salieno.json` is the marketplace packaging manifest:
kind: "theme",theme_target: "storefront",version,requires_core, andentry. The marketplace uses it to identify and route the artifact.
Their version fields must match at publish time. Details and field-by-field reference in Anatomy.
Distribution: signed artifacts, and updates that keep customisation#
Themes are not copied around by hand in production. An approved marketplace developer creates a theme product, cuts a version whose semver is strictly greater than the last, uploads a zip, and the marketplace scans it, a human reviews it, and Core Ed25519-signs it server-side (Core pins the public key). Installs download the artifact, verify the signature, and atomically swap the theme directory into place.
The rule that matters most to you and your users: updates swap files only. Admin page-builder content lives in the database keyed by theme name and is never touched by an update. So shipping a new version of your theme never wipes a customer's edited hero copy, pricing text, or section content. Publishing covers versioning, signing, and the update flow end to end.
For local development you skip all of that: drop your folder in resources/views/themes/ and activate it in Admin → Frontend → Themes, or scaffold a starting point with php artisan template:scaffold <name>. That is exactly what the Quickstart does next.
The road ahead#
This guide is a complete handbook — every article builds a piece of a shippable theme:
- How Salieno themes work (you are here) — the architecture and mental model.
- Create your first theme — scaffold to running in minutes.
- Theme anatomy & the two manifests — directory layout,
theme.json,salieno.json. - Layouts and the page shell —
app,frontend, and the Livewire slot bridge. - CSS, JavaScript & images — assets,
themeAsset, precompilation, security. - Building sections —
sections.json, the render pipeline, editable content. - Pages and how they resolve — home, pricing, domains, cart, checkout, product, KB, errors.
- Authentication pages — login/register/reset with Livewire, preserving bindings.
- Components: using and overriding `x-theme.*`.
- Helper & template reference — every theme helper.
- Colours, dark mode & the mode switcher.
- Testing your theme — local install and the completeness checklist.
- Publishing & updating on the marketplace.
- Design & quality guidelines — the look that reads as $10B, not generated.
Next: Create your first theme — scaffold a working theme and see it running.