Skip to main content
A theme isn’t limited to the landing page. With a small entry class it can register routes and render any template it ships, which is how you add pages such as /features, /about or a pricing explainer.

What you need

1. Declare the class

static/composer.json

2. Write the entry class

static/src/Theme.php
The class is resolved from the container, so anything you type-hint is injected. boot() runs only while your theme is the published one.

3. Add a request handler

static/src/FeaturesView.php

4. Add the template

static/templates/features.twig

5. Install and clear the cache

Routes are discovered by scanning, and the result is cached when CACHE=true. During development set CACHE=false; in production clear the cache from Status → Clear cache after publishing a theme that adds routes.

Passing data to the template

To show plans on a page of your own, dispatch the same command the landing page uses:
Check the command’s setters in the source before copying this: they’ve changed between versions, and the landing handler is the reference for what the current one expects.

Rules

  • Keep the theme’s pages public. A page that needs a signed-in user belongs in a plugin under /app, not a theme.
  • Handle the landing-page switch. If the public site is disabled, redirect rather than rendering.
  • Don’t do heavy work. Marketing pages should hit the database rarely, and never call a third-party API on render.
  • Namespace your classes under your own vendor, so two themes can be installed at once without colliding.

Troubleshooting

The theme isn’t published, the cache is stale, or addPath() doesn’t cover the handler’s directory.
Run composer require acme/aurora, and check that the manifest’s namespace, the PSR-4 map and the class all agree.
#[Middleware(ViewMiddleware::class)] is missing, so the ViewResponse was never rendered.