Template Library and Website Templates

Two levels of ready-made design ship with TruCMS: the template library (sections and full pages an editor inserts from the builder) and website templates (complete demo sites with pages, menus, theme options and sample data). A plugin can add layouts to the first; the second is extended with seeders or .trucms.zip packages.

Vocabulary

TermMeaningStorage
Template sourceA catalog of layouts: the active theme (core) or one moduleFiles: catalog.php + sections/
Section (kind section)An HTML fragment an editor appends to a page (heroes, feature rows, FAQs…)page_templates row
Page (kind page)A composition of sections ("Home", "Pricing")page_templates row
Template assetCSS for a layout (shared ui-* kit and per-layout files)template_assets row, key {Source}:{section}
Website template / nicheA whole site; a NicheSeeder subclass or an imported packagewebsite_templates row

Ship layouts from a plugin

1. Folder and catalog

Modules/Playground/
  resources/page-templates/
    catalog.php                          what the library lists
    sections/playground/note-strip.html  the markup
    sections/playground/note-strip.css   optional layout CSS (stored in the database)
  database/seeders/PlaygroundPageTemplatesSeeder.php

resources/page-templates/catalog.php

return [
    'sites' => [],                       // optional demo sites that group pages (see Booking)
    'heroes' => [],                      // sections shown in the "Hero" category: name, thumbnail, premium?, new?
    'sections' => [
        'playground/note-strip' => [     // = path of sections/playground/note-strip.html
            'name' => 'Note strip',
            'group' => 'Playground',     // category in the library
            'thumbnail' => 'placeholder.webp',   // a photo from the theme's template images until a screenshot exists
        ],
    ],
    'library_pages' => [                 // full-page layouts built from sections
        'playground-notes-page' => [
            'name' => 'Playground notes page',
            'category' => 'Playground Pages',
            'thumbnail' => 'placeholder.webp',
            'description' => 'A page that shows one note.',
            'sections' => ['playground/note-strip'],
            // 'bound' => 'note',        // wrap the page in a data-bound element so one record is picked for every section
        ],
    ],
];

sections/playground/note-strip.html

<section class="pg-note-strip py-5">
    <div class="container">
        <h2 class="mb-4">Latest note</h2>
        <x-shortcode-playground-note note=""></x-shortcode-playground-note>
    </div>
</section>

sections/playground/note-strip.css

.pg-note-strip { background: var(--bs-light); }

2. The seeder

database/seeders/PlaygroundPageTemplatesSeeder.php

namespace Modules\Playground\Database\Seeders;

use Database\Seeders\Support\TemplatePublisher;
use Database\Seeders\Support\TemplateSources;
use Database\Seeders\Support\ThemeTemplateCatalog;
use Illuminate\Database\Seeder;

class PlaygroundPageTemplatesSeeder extends Seeder
{
    public function run(): void
    {
        (new TemplatePublisher)->publish(new ThemeTemplateCatalog(
            config('dotartisan.front_theme', 'echo'),
            TemplateSources::module('Playground'),
        ));
    }
}

Call it from your module's DatabaseSeeder (Booking and TruCommerce do) and run it after installing or changing layouts:

php artisan db:seed --class="Modules\Playground\Database\Seeders\PlaygroundPageTemplatesSeeder"

What happens: running the seeder creates one section and one page row (deterministic UUIDv5 keys, so a second run updates instead of duplicating); the layout CSS appears as a template_assets row Playground:playground/note-strip; the editor's listing endpoint (admin.page-builder.load) returns the section by name; inserting it (admin.page-builder.templates.show) returns the rendered HTML and the CSS the layout needs. Removing a CSS file from the source deletes its row on the next publish.

Layouts only exist where the plugin is installed

Seed from the module's own seeder so the library never lists layouts whose shortcodes are missing. TemplateSources::modules() only scans enabled modules that contain a catalog.php.

Rules for layout markup (enforced by tests)

The full rule set is in .ai/rules/page-templates.md, components.md and bindings.md.

How CSS reaches the page

  1. Pages store markup only.
  2. At render, TemplateAssets reads the class names used in the page, CssPurger keeps only the matching rules from the stored kit and layout CSS, and <x-template-styles> prints them into the template_styles stack after the theme stylesheet (so layout rules beat Bootstrap defaults).
  3. The shared kit is compiled from resources/themes/echo/assets/sass/components/_page-sections.scss by npm run templates:kit into page-templates/kit.css and stored by PageTemplatesSeeder.
  4. A CSS rule removed from a layout restyles pages that were inserted earlier (pages keep their old markup but get the current stored CSS by class).

Screenshots

A layout card shows /modules/{kebab-name}/images/page-templates/previews/{key}.webp when the file exists in the module's resources/assets/images/page-templates/previews (copied to public/modules/{kebab}/images by the Vite static copy for the bundled modules; copy/publish them yourself for a plugin), otherwise the catalog's stock thumbnail. The helper scripts scripts/capture-template-previews.py and capture-niche-previews.py render previews against a scratch database (_previews); they never run against the real database and must not run in parallel.

Library sections that show live records

Use data-bound markers in the section HTML (data-bound="note", data-bind="title"…) and, for a whole page about one record, the catalog's 'bound' => 'source-key'. Bind only when most of the section is source data; leave authored copy (reviews, hours, comparison tables) alone.

Website templates (whole demo sites)

Distribute a site without touching core: .trucms.zip

Third parties package a site with Appearance → Website templates → Export (or the template library export) and customers import it through the same screen (permission import website templates). The package (app/Services/WebsiteTemplates/Packages) is JSON plus assets, validated as data only:

Checklist for a plugin that ships layouts

  1. Catalog + sections + CSS in resources/page-templates; seeder wired into the module's DatabaseSeeder.
  2. Every shortcode used has explicit closing tags and sensible empty defaults (a freshly inserted layout must render before the editor fills it).
  3. Theme colours only; reduced-motion fallback for motion.
  4. Run the seeder in a test and assert the rows exist , including that a second run changes nothing.
  5. Re-run the seeder after every layout change; existing pages keep old markup.