Skip to content

Packaging and publishing a theme

Packaging

A theme is packaged as a zip whose folder name is the theme name. The zip has the theme folder at its root:

example-theme-1.0.0.zip
└── example-theme/
    ├── theme.json
    ├── screenshot.png
    └── Resources/
        ├── views/layout.html.twig
        └── dist/style.css

What should not go into the package: your own source files (src/, node_modules/), .git, build configurations. What cannot be in the package, rather than what should not: .php, .phtml, .phar files, .htaccess, .user.ini, web.config and cp-core/ or cp-includes/ paths. If any of these is present the package is rejected.

Contract check

Before installation the package is checked automatically (details: Theme contract). Common rejection reasons and fixes:

Message Fix
Theme directory name must be kebab-case Change the directory name to the form example-theme (lowercase, hyphen)
theme.json is missing or invalid The file is missing or the JSON is broken; check with a JSON validator
theme.json is missing a "name" / "version" field Add the field; the version cannot be 0.0.0
Missing Resources/views/layout.html.twig Add the main template
theme.json entry file "…" does not exist under Resources/ The entry.css/entry.js path is written relative to Resources/; does the file exist
Package must not include server-side code or server config Remove .php, .htaccess etc. files from the package
Package must not include core paths The package contains cp-core/ or cp-includes/
… must not declare namespace App\ A theme cannot contain PHP; remove the file

In addition, a syntax check (ThemeSourceLinter) is made when templates are saved: a broken Twig file gives a warning before saving (it can still be saved with confirmation).

Installation ways

  1. Zip upload: AACP → Appearance → Add theme → choose the zip. If a theme with the same name exists, confirmation to overwrite is asked. Installation writes only under cp-content/themes/, never touching the core.
  2. Store: AACP → Modules → Module store (for themes Appearance → Theme store); browse the signed catalogue with images and install.
  3. By hand: copy the folder under cp-content/themes/. It appears in the AACP theme list.

After installing, activate it from AACP → Appearance → Theme list. Activation rebuilds the Twig paths, so it refreshes the cache; this may take a few seconds.

Resilience at runtime

  • A broken theme.json drops the theme from the list and does not affect others.
  • If the active theme setting is invalid, the system falls back to the fallback theme (cpalius-website); error pages are drawn with the fallback theme even when the database is down.
  • A template error is caught at the response layer like every other error; it does not bring down the whole site.

Publishing to the store and the catalogue

The store offers a signed catalogue (extensions/index.json + index.sig): each package's version, SHA-256 digest and screenshot are in the catalogue. Before the downloaded package is extracted, the digest is verified; the catalogue is checked by signature, expiry and a non-decreasing serial number. This way someone who has taken over the catalogue cannot present an old, vulnerable version as new.

A direct publishing flow to the store for third-party developers is not open yet (it is on the roadmap). Until then you can distribute your theme as a zip directly to your customers or contact the CPalius team for publication.

Versioning and compatibility

  • The theme.json version must be MAJOR.MINOR.PATCH.
  • When you override a template, your theme breaks if the module's template variables or block names change. Follow the "concerns theme authors" notes in module or core release notes.
  • Copying and changing the templates in the default theme is the most fragile way: when the default theme changes, you have to carry the difference by hand. Override as few templates as possible (Githeme overrides only three files and takes the rest from the default).

Theme development checklist

Structure

  • The directory name is kebab-case; theme.json name, version, entry are filled
  • screenshot.png exists (store and theme list image)
  • No .php, .htaccess, cp-core/ in the package

Template

  • layout.html.twig defines the title, meta_description, meta_robots, stylesheets, content, javascripts blocks
  • The theme.head.end and theme.footer.before hooks are present
  • On the front end cp_user(), cp_is_granted(), cp_flashes() are used (not app.user)
  • User data is escaped; |raw only on sanitized content
  • Inline scripts/styles carry a nonce

Features

  • Menu locations are declared in theme.json and drawn in the template
  • Widget areas are drawn with cp_widget_area
  • Colours use the --cp-* variables (so the administrator's colour choice works)
  • cp_font_stylesheet() is linked after the theme's CSS
  • Cookie consent: cp_consent_mode() and cp_cookie_consent()
  • Language switcher and cp_hreflang_links()
  • TR and EN translations

Quality

  • No external CDN calls (font, icon, script)
  • Mobile, dark/light, print views were tried
  • Accessibility: skip link (skip-link), focus visibility, contrast, lang attribute
  • It is drawn even when a module is off (cp_path_if_exists, cp_widget_area may return empty)
  • Templates that depend on the default theme are documented
Last updated: 13.10.2026