Theme contract
A theme is the package that draws the site's front end. A module decides what is shown (a blog post, a forum topic), the theme decides how it is shown. This split is deliberate: when you change the theme, modules and data do not change, only the look does.
Themes are completely separate from the core. A theme author can use Vite, esbuild or plain CSS; CPalius serves only the compiled output and does not compile theme assets.
Folder layout
cp-content/themes/example-theme/
theme.json manifest (required)
screenshot.png store and theme list image
Resources/
views/
layout.html.twig front-end main template (required)
partials/header.html.twig
partials/footer.html.twig
blog/ forum/ page/ ... module pages (optional overrides)
dist/ compiled CSS and JS (declared in theme.json)
translations/ theme-specific translations (optional)
The directory name must be kebab-case (example-theme, cpalius-website); otherwise the package is rejected.
theme.json
{
"name": "Example Theme",
"version": "1.0.0",
"description": "Short description.",
"author": "Me",
"screenshot": "screenshot.png",
"entry": {
"css": ["dist/style.css"],
"js": ["dist/main.js"]
},
"supports": ["blog", "menu", "pages"],
"menu_locations": {
"main_header": "Main header",
"footer": "Footer"
}
}
| Field | Required | Meaning |
|---|---|---|
name |
yes | Display name |
version |
yes | MAJOR.MINOR.PATCH; 0.0.0 or empty is invalid |
description, author |
no | Shown in the theme list |
screenshot |
no | Preview image (path relative to the theme folder) |
entry.css, entry.js |
no | The published CSS/JS files. Written relative to Resources/ and the files must exist |
supports |
no | The modules whose templates the theme brings (blog, forum, pages, menu, roadmap…) |
menu_locations |
no | The theme's menu locations (key → display name); see Menu, widgets and appearance |
All fields except name are read as optional: a theme with an incomplete manifest does not break the list, while a broken theme.json drops that theme from the list (counts as empty) and does not affect other themes.
Package rules
A theme package (zip or directory) is checked by code before activation. It is rejected if any of the following holds:
theme.jsonis missing or is invalid JSON.nameorversionis missing.Resources/views/layout.html.twigis missing.- A CSS/JS file declared in
entryis missing. - The package contains server-side code:
.php,.phtml,.phar,.phtextensions or.htaccess,.user.ini,web.configfiles. - The package contains a
cp-core/orcp-includes/path, or a zip entry has..(path traversal) or an absolute path. - It declares the
App\namespace in its own file.
This means a theme cannot run code on the server: it carries only Twig, CSS, JS and images.
A theme is the presentation layer that draws the front end. A theme that could run server code would be a back door with access to every module and every administrator account. If you need logic, write a module.
Template resolution: @Theme
Modules call their templates not directly by the theme's name but through the shared @Theme namespace: @Theme/layout.html.twig, @Theme/blog/show.html.twig. The core sets up this namespace like this:
- The active theme's
Resources/views/folder is the first entry of the@Themepath. - Other installed themes are added in order as fallbacks.
Result: if the active theme defines a template, it is used; if not, it comes from the fallback themes (usually the default theme). That is why you can write a theme partially:
githeme/Resources/views/
layout.html.twig ← overridden
partials/header.html.twig ← overridden
(every other template comes from the default theme)
The Githeme theme overrides only layout, partials/header and the portal hero templates; it takes the rest from the default theme, so the default theme must stay installed.
Each theme also has its own namespace (@ExampleThemeTheme: the PascalCase form of the directory name + Theme), so a theme can explicitly call another theme's template.
Twig paths are set up at build time. When the active theme is changed the cache needs to be rebuilt; the AACP theme screen does this itself after saving. If you put files by hand, run cache:clear.
Default theme and fallback theme
The theme named cpalius-website is the fallback theme: it is used if the active theme setting (core.active_theme) is invalid. Whatever your own theme does, this theme must stay intact; do not delete it.
Theme-specific translations
Resources/translations/messages+intl-icu.<lang>.yaml adds the active theme's translations. These files are added at the very start of the translation list; that is, core and module translations override them and a theme can never change a core key. For theme-specific keys use a prefix like theme..
Getting started
Copying an existing theme folder and changing its name and theme.json content is the quickest way. Then:
- Template structure and helpers: Templates and Twig helpers
- Menu, widget area, colour and font: Menu, widgets and appearance
- CSS and JS: CSS build and assets
- Publishing: Publishing a theme