Creating a TruCMS Theme

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.

Choose the lightest tool

You want to change…UseSurvives updates?
Colours, fonts, spacing, header and footer layoutAppearance → Theme options (160+ options)Yes, stored in the database
A few CSS detailsSettings → Integration custom header codeYes
How a plugin's pages lookA view override in a theme (see below)Yes, if it lives in your own theme
Page layoutsThe page builder and template libraryYes
Markup of headers, layouts, blog or account screensA child theme of EchoYes
Anything, by editing resources/themes/echo directlyDon'tNo: updates replace Echo's files

Anatomy of a theme

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/images

The manifest

resources/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" }
}
KeyMeaning
nameFolder name and theme id. Lower-case letters, digits, - and _
extendsParent theme. Views missing from this theme are looked up in the parent, then its parent
asset-pathPublic folder for static assets (public/themes/echo); used by theme_url()
defaultsThe 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.

How the active theme is chosen

How views resolve

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.

Build a child theme of Echo

  1. Create the folder and manifest

    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.

  2. Override only what you change

    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.

  3. Give it a stylesheet entry

    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.

  4. Static assets

    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.

  5. Activate
    php artisan theme:refresh-cache
    # set APP_FRONT_THEME=harbor in .env, then:
    php artisan optimize:clear
    npm run build

    Open Appearance → Theme options, save once, and use Advanced → Regenerate Theme CSS after later builds.

What is tied to the theme name

Plan for these before switching a live site to a new theme name:

ThingEffect of a new theme name
Theme option valuesStored per theme (theme_options.{theme}). The new theme starts from its schema defaults; copy values over if you need them
Compiled CSSWritten to storage/app/public/css/themes/{theme}/app.css
Template libraryPage-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 templatesNot filtered by theme, but they were designed for Echo's options and sections; check them before offering them under another theme
Template packagesExports record the active theme as a requirement

Theme helpers in Blade

HelperUse
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, $themeStylesheetUrlShared with every public view by FrontTheme

Layouts available to plugins

Plugin views should wrap themselves in the layout components rather than extend a theme file directly, so they work under any theme:

ComponentRenders
<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