TruCMS has one public theme, Echo, built to look very different from site to site through its options. Before you write a theme, check whether options, a website template or a few view overrides already get you there. When you do need your own markup, build a child theme of Echo.
| You want to change… | Use | Survives updates? |
|---|---|---|
| Colours, fonts, spacing, header and footer layout | Appearance → Theme options (160+ options) | Yes, stored in the database |
| A few CSS details | Settings → Integration custom header code | Yes |
| How a plugin's pages look | A view override in a theme (see below) | Yes, if it lives in your own theme |
| Page layouts | The page builder and template library | Yes |
| Markup of headers, layouts, blog or account screens | A child theme of Echo | Yes |
Anything, by editing resources/themes/echo directly | Don't | No: updates replace Echo's files |
resources/themes/
default/ shared parent: static images and the CKEditor build; every theme extends it
admin/ the admin panel (extends default)
echo/ the public site (extends default)
theme.json manifest + the theme-option schema ("defaults")
layouts/ app-layout, fullwidth-layout, guest-layout, error-page
components/ header and footer presets, breadcrumbs, cookie banner ...
menu/ pages/ blog/ posts/ shortcodes/ widgets/ user/ auth/ emails/ errors/
vendor/ overrides of package and module views
page-templates/ the section library used by the page builder
assets/sass, assets/js, assets/imagesresources/themes/echo/theme.json (without the defaults block)
{
"name": "echo",
"extends": "default",
"asset-path": "themes/echo",
"category": "front",
"require": "1.0.0",
"description": "The default TruCMS frontend theme.",
"version": "2.0.0",
"defaults": { "...": "theme option schema" }
}| Key | Meaning |
|---|---|
name | Folder name and theme id. Lower-case letters, digits, - and _ |
extends | Parent theme. Views missing from this theme are looked up in the parent, then its parent |
asset-path | Public folder for static assets (public/themes/echo); used by theme_url() |
defaults | The option schema behind Theme options. See Theme options and CSS pipeline |
Theme manifests are cached. After adding or editing a theme.json, run php artisan theme:refresh-cache.
config('dotartisan.front_theme'), set from APP_FRONT_THEME (default echo). The FrontTheme middleware activates it on every public route. There is no theme switcher in the admin; change the environment value and clear the config cache.admin_theme setting (default admin), activated by AdminTheme.Theme::set('echo') yourself before rendering theme views there.The active theme's folder and every parent are added to Laravel's view paths, so view('pages.show') finds resources/themes/{active}/pages/show.blade.php first, then the parent's copy. A child theme only contains the files it changes.
Namespaced module views follow the same idea: booking::front.services is looked up first in resources/themes/{active}/vendor/booking/front/services.blade.php, then in the module. That is how a theme restyles a plugin without touching it.
resources/themes/harbor/theme.json
{
"name": "harbor",
"extends": "echo",
"asset-path": "themes/harbor",
"category": "front",
"require": "1.0.0",
"description": "Harbor: a child theme of Echo.",
"version": "1.0.0",
"defaults": {}
}Then copy Echo's whole defaults object into yours. The option schema is not inherited: ThemeOptionsSchema reads only the active theme's theme.json, so an empty defaults leaves you with a bare fallback options screen and theme_option() without defaults.
Copy a view from Echo to the same relative path in harbor/ and edit it, for example components/headers/logo-left.blade.php or vendor/booking/front/partials/summary.blade.php. Keep the variables and slots the original expects.
Create resources/themes/harbor/assets/sass/app.scss (it can start by importing Echo's Sass) and add it to the input list in the root vite.config.js. The theme-option compiler looks for resources/themes/{theme}/assets/sass/app.scss in the Vite manifest; without it, saved options never reach your theme's CSS. Echo's layout loads resources/themes/echo/assets/sass/app.scss directly, so also override layouts/app-layout.blade.php (and the other layouts you use) to load your entry.
Put images in resources/themes/harbor/assets/images and add a copy target to vite.config.js like Echo's, so they land in public/themes/harbor.
php artisan theme:refresh-cache
# set APP_FRONT_THEME=harbor in .env, then:
php artisan optimize:clear
npm run buildOpen Appearance → Theme options, save once, and use Advanced → Regenerate Theme CSS after later builds.
Plan for these before switching a live site to a new theme name:
| Thing | Effect of a new theme name |
|---|---|
| Theme option values | Stored per theme (theme_options.{theme}). The new theme starts from its schema defaults; copy values over if you need them |
| Compiled CSS | Written to storage/app/public/css/themes/{theme}/app.css |
| Template library | Page-template rows are tagged with the theme they were published for (echo). The editor's library lists only rows for the active theme or with no theme, so Echo's sections and pages are hidden under harbor until you publish templates for it |
| Website templates | Not filtered by theme, but they were designed for Echo's options and sections; check them before offering them under another theme |
| Template packages | Exports record the active theme as a requirement |
| Helper | Use |
|---|---|
theme_option('key', $default) | Read an option of the active front theme (stored value → schema default → your default) |
theme_url('/themes/echo/images/x.png') | URL of a static theme asset |
menu($idOrName, $type) | Render a menu with one of the templates in menu/ |
$appDir, $currentLanguage, $themeStylesheetUrl | Shared with every public view by FrontTheme |
Plugin views should wrap themselves in the layout components rather than extend a theme file directly, so they work under any theme:
| Component | Renders |
|---|---|
<x-front-layout> | The standard public page with header, breadcrumbs and footer |
<x-front-fullwidth-layout> | Full-width public page |
<x-front-guest> | Sign-in style pages |
<x-admin-layout>, <x-admin-guest> | Admin pages |