Theme anatomy & the two manifests
The complete theme directory layout plus theme.json and salieno.json field by field, and which files Core and the marketplace actually require.
On this page
Theme anatomy & the two manifests#
A Salieno theme is a self-contained directory of Blade views, precompiled assets, and JSON manifests. Because Core ships no default theme and privileges none, the fallback view finder only steps in when a view is missing — so anything your theme doesn't ship becomes a neutral Core holding page. That makes the layout of the directory, and the two manifests that describe it, the contract you're building against. This article is the map: what goes where, which files are required, and what every manifest field means.
If you haven't yet, read the overview for how resolution and distribution work, then scaffold a starting point per the quickstart. This article assumes you have a theme folder in front of you.
Where a theme lives#
Every theme is a single directory named after the theme:
resources/views/themes/{name}/The directory name is the theme's identity everywhere in Core — it's what activeThemeName() returns, what session('template') ?? gs('active_theme') stores, what themeViewName('home') prefixes to themes.{name}.home, and the DB key under which admin page-builder content is stored. Keep it lowercase, hyphen-free, and stable: renaming the folder orphans stored customisation.
The directory tree#
Here is the shape of a complete theme, taken from the first-party salieno reference theme. Not every folder is mandatory (see Required vs optional below), but this is the full surface a storefront can present:
resources/views/themes/{name}/
├── theme.json # runtime manifest Core reads (REQUIRED)
├── salieno.json # marketplace packaging manifest (REQUIRED to publish)
├── sections.json # section order + page-builder definitions (REQUIRED)
│
├── layouts/
│ ├── app.blade.php # the page shell — REQUIRED, and the manifest "entry"
│ └── frontend.blade.php # optional inner content layout
│
├── home.blade.php # top-level page views (home, pricing, contact, …)
├── pricing.blade.php
├── contact.blade.php
├── page.blade.php # generic CMS page
├── maintenance.blade.php # rendered via activeTheme().'maintenance'
├── cookie.blade.php policy.blade.php unsubscribe.blade.php affiliate.blade.php
│
├── sections/ # one blade per section slug in sections.json
│ ├── hero.blade.php
│ ├── pricing.blade.php
│ ├── faq.blade.php
│ └── partials/ # shared bits used by several sections
│
├── partials/ # header, footer, render-sections, breadcrumbs, …
│ ├── header.blade.php
│ ├── footer.blade.php
│ ├── render-sections.blade.php
│ └── illustrations/
│
├── components/theme/ # optional overrides of the x-theme.* library
│ ├── card.blade.php
│ ├── btn-primary.blade.php
│ ├── pricing/ compare/ ui/
│
├── auth/ # Livewire auth page bodies
│ ├── login.blade.php register.blade.php
│ ├── forgot-password.blade.php reset-password.blade.php
│ ├── verify-email.blade.php two-factor-verify.blade.php
│ └── partials/
│
├── cart/
│ ├── index.blade.php checkout.blade.php confirmation.blade.php
├── domains/
│ ├── index.blade.php search.blade.php configure.blade.php transfer.blade.php pricing.blade.php
├── products/
│ ├── category.blade.php order.blade.php
├── store/
│ └── compare-page.blade.php
├── kb/ # knowledge base: index, category, article, search, partials/
├── blog/ # blog: index, details, partials/
│
├── errors/
│ ├── 403.blade.php 404.blade.php 419.blade.php
│ ├── 429.blade.php 500.blade.php 503.blade.php
│
└── assets/ # PRECOMPILED, copied to public/ on install
├── css/theme.css
└── js/theme.jsA few facts worth internalising now:
- Views map to pages by name.
home.blade.phpbacks the home route, thecart/folder backs the cart routes,errors/404.blade.phpbacks a 404, and so on. Pages and how they resolve walks every one. - `sections/` and `sections.json` move together. The array in
sections.jsonnames slugs; each slug needs a matchingsections/{slug}.blade.php. A slug with no blade is silently skipped, not fatal. See Building sections. - `assets/` ships already-built CSS/JS. Uploaded themes run precompiled — no
@vite, no@tailwind/@applyat runtime. Reference files withthemeAsset('css/theme.css'). Details and the copy-time security allowlist are in CSS, JavaScript & images. - Seed data for sections is not part of the theme. When admin content is empty, section blades fall back to hardcoded defaults or to Core's own
resources/data/*.jsonseeds — those live in Core, not in your theme folder. Your job is to render sensibly when a field is empty.
theme.json — the runtime manifest#
theme.json is what Core reads at runtime to describe, gate, and colour the theme. It lives at the theme root. Here is the reference theme's file, trimmed to one representative colour set:
{
"name": "Salieno Theme",
"version": "1.2.0",
"author": "Salieno Team",
"description": "A premium, fast-loading hosting & domains theme — clean typography, accessible components, mobile-first layout, and light & dark modes.",
"preview": "preview.jpg",
"requires_salieno": "1.0.0",
"supports": {
"sections": true,
"dark_mode": true,
"rtl": false
},
"settings": {
"show_mode_switcher": true
},
"colors": {
"primary": "#ef4444",
"secondary": "#18181d",
"neutral_dark": "#08080a",
"neutral_light": "#ffffff",
"footer_bg": "#111114",
"footer_text": "#fafafa"
}
}Field by field:
| Field | Type | What it does |
|---|---|---|
name | string | Human-readable display name shown in Admin → Frontend → Themes. Required — validation fails without it. This is the label, not the folder name. |
version | string (semver) | The installed version. Required. Must equal salieno.json's version at publish time; the marketplace enforces strictly-increasing versions on update. |
author | string | Attribution shown in the gallery. Optional. |
description | string | One-line summary in the gallery card. Optional, strongly recommended. |
preview | string | Filename of the preview image at the theme root (e.g. preview.jpg), shown as the gallery thumbnail. Optional. |
requires_salieno | string (min version) | The minimum Core version this theme needs. Core refuses to install a theme whose requires_salieno is newer than the running Core, with a clear "update Core first" message. Absent means "no minimum" and is allowed. This is the one field that distinguishes a deliberately minimal theme from one built for a Core you don't have. |
supports.sections | bool | Declares the theme renders admin-editable sections. |
supports.dark_mode | bool | Declares the theme ships a light + dark palette. Pair with settings.show_mode_switcher. See Colours, dark mode & the mode switcher. |
supports.rtl | bool | Declares right-to-left support. |
settings | object | Free-form theme options Core surfaces/reads. The reference uses show_mode_switcher to toggle the light/dark control. |
colors | object | Named colour tokens for the theme. Values are hex or rgba(). They document the palette and back the admin colour preview; you still author the real CSS in assets/css. Keys like primary, secondary, neutral_*, footer_*, button*, and svg_* are conventions from the reference theme, not a fixed schema — use the ones your CSS consumes. |
Core reads these via the themeMeta(key, default) helper (backed by theme.json); the full helper set is catalogued in the helper reference. Validation is minimal by design: a missing theme.json, invalid JSON, or an empty name/version blocks installation; everything else is optional metadata.
salieno.json — the marketplace packaging manifest#
salieno.json is the packaging descriptor the marketplace reads when you submit a version. It is not consulted at page-render time — theme.json is the runtime manifest — but it is what identifies the archive as a theme and states its compatibility. Here is the reference file in full:
{
"kind": "theme",
"theme_target": "storefront",
"name": "Salieno",
"version": "1.2.0",
"requires_core": ">=1.0.0 <2.0.0",
"entry": "layouts/app.blade.php",
"description": "The default Salieno Core storefront theme — a fast, accessible hosting and domains front end with light and dark modes, built entirely from sections.",
"author": "Salieno",
"license": "proprietary",
"homepage": "https://salieno.com",
"support_url": "https://salieno.com/support",
"requires": {
"php": ">=8.2"
}
}Field by field:
| Field | Type | What it does |
|---|---|---|
kind | string | Must be "theme". This is how the marketplace tells a theme package from an extension. |
theme_target | string | Where the theme applies. Storefront themes use "storefront". |
name | string | Package name shown in the marketplace listing. |
version | string (semver) | The version of this package. Must match theme.json's version, and each new marketplace version must be strictly greater than the last published one. |
requires_core | string (semver range) | The Core versions this theme supports, e.g. ">=1.0.0 <2.0.0". The marketplace and installer use this to keep a theme off a Core it can't run on — the sibling to theme.json's requires_salieno. |
entry | string | The theme's shell view, relative to the theme root — layouts/app.blade.php. |
requires.php | string (range) | Minimum PHP the theme's compiled assets/views assume, e.g. ">=8.2". |
description, author, license, homepage, support_url | string | Listing metadata for the marketplace page. Optional but expected on a public submission. |
At submission the marketplace scans the archive (locating salieno.json at the zip root or one level down), a human reviews it, and Core signs it server-side with Ed25519 before publishing. Installs verify that signature and atomically swap the theme directory — file swaps only, so admin page-builder content, which is keyed by theme name in the database, is never touched by an update. The mechanics and the versioning rules are in Publishing & updating on the marketplace.
Required vs optional#
There are two overlapping notions of "required": what Core needs on disk to treat a folder as a working theme, and what the marketplace needs to accept a package.
Required for a theme to install and validate (ThemeService):
layouts/app.blade.php— the page shell / manifest entry.sections.json— must be present and valid JSON.theme.json— present, valid JSON, with non-emptynameandversion.
Required to publish additionally needs salieno.json with a matching version, a kind of theme, and a requires_core range that admits real Core versions.
Everything else is optional but load-bearing. Because Core has no default theme to borrow from, any public view you omit renders as a bare Core holding page, not a styled fallback. A theme meant for real use should therefore ship the whole surface: the section blades named in sections.json, the auth pages, cart/domains/products/kb/blog pages, all six errors/ codes, and the assets they reference. The testing checklist is the completeness audit for exactly this.
Two folders are genuinely optional in behaviour, not just in validation:
components/theme/— you only ship an override when you want to replace a Core basex-theme.*component; otherwise Core's version renders. See Components: using and overriding x-theme.*.layouts/frontend.blade.php— an inner content layout is a convenience, not a contract; onlyapp.blade.phpis required.
Next#
You now know the map and the manifests. Next, build the shell everything renders into: Layouts and the page shell.