Developers
Architecture
The big picture#
browser → web server (.htaccess / nginx) → index.php (public site)
→ padmin/index.php (admin)
both load inc/bootstrap.php: storage, settings, sessions, CSRF, auth1. Web server layer#
The root .htaccess (or the nginx config) does five things, in order:
- Blocks internals:
data/,inc/,templates/,themes/, any_folder, dotfiles,README.md. - Allows PHP only for two front controllers (
index.php,padmin/index.php). PHP inuploads/andassets/never runs. - Serves existing files directly (CSS, JS, images, a generated
sitemap.xml, thisdocs/site). - Routes
/padmin…to the admin. - Routes everything else to
index.php.
2. The installer gate#
timber_installed() is true when data/users.json has at least one account and data/settings.json exists. Until then, both front controllers redirect to /padmin/install, which writes settings, menus, three starter versions, sample content, forms and your first Administrator account via timber_install().
3. The public front controller#
In order:
/v/{slug}/…version previews (live/preview status, a logged-in admin, or the share key).noindex,no-store.- Redirects, then maintenance mode (503 for visitors).
- A live version must exist.
- The route table:
| Route | Handler |
|---|---|
/ | The page chosen as homepage, else the live version's home with the latest posts |
blog, blog/{slug}, partials/posts | Blog index, post, "load more" fragment |
a form's path, forms/submit/{key} | Form page and submission |
sitemap.xml, feed.xml, robots.txt, favicon.ico | Generated live |
{page slug} | A published page |
| anything else | Branded 404 |
4. Rendering#
partial($template, $vars) // include a template, extract($vars, EXTR_SKIP)
render($template, $vars) // buffer a partial into $content, then wrap it in layout
section($key) // render one homepage section of the current version
region('header'|'footer') // a Global block, else the theme's parts/…Lookup order: themes/{current theme}/{name}.php, then templates/{name}.php — which is how a theme inherits the shared page/post/form templates and overrides only what it needs.
The current version is the live one unless the request is a /v/{slug} preview; vc(), href() and theme_asset() all read from it, so live and preview render identically.
5. The admin#
/padmin/{section}/{action}/{id} → a $routes map → padmin/views/{file}.php. Each view is controller and template in one file: handle actions at the top, render with admin_page() at the bottom. htmx handles saves and toasts; Alpine handles editors and drag-sort. See Extending Timber.
6. Dates and time#
UTC everywhere; ISO 8601 strings in storage. A post dated in the future is scheduled: it's hidden until then, no cron required.