Development
The Development Loop
Section titled “The Development Loop”Most features follow the same three-step cycle:
- PHP — fetch data from WordPress, build a context array
- Twig — render the context as HTML
- SCSS/JS — style and add interactivity
Starting the dev server
Section titled “Starting the dev server”Run this from the theme root to start Vite with hot-module replacement:
ddev npm run devOpen your site in the browser. SCSS changes hot-reload without a page refresh; PHP/Twig changes trigger a full reload.
PHP → Twig Data Flow
Section titled “PHP → Twig Data Flow”WordPress routes a request to a PHP template (e.g. single.php). That file builds a context array and passes it to Twig:
use Timber\Timber;
$context = Timber::context(); // global data (site, theme, user)$context['post'] = Timber::get_post(); // the current post
Timber::render('templates/single.twig', $context);Every key you add to $context becomes a variable in the Twig template:
{# views/templates/single.twig #}
{% extends 'layouts/base.twig' %}
{% block content %} <h1>{{ post.title }}</h1> {{ post.content }}{% endblock %}All templates extend layouts/base.twig, which provides the full HTML document with header, footer, and a {% block content %} for the page body.
Global context
Section titled “Global context”A handful of variables are available in every Twig template without you adding them:
| Variable | Type | Description |
|---|---|---|
site | Timber\Site | Site name, URL, etc. |
theme | Timber\Theme | Theme directory, URI |
user | Timber\User | null | Logged-in user or null |
To add your own global data (available on every page), use app/Hook/TimberHook.php. See the Context guide for details.
Where Does New Code Go?
Section titled “Where Does New Code Go?”| What you’re building | Location |
|---|---|
| WordPress hook / filter | app/Hook/ |
| Extending a Timber post/term | app/Classmap/ |
| A reusable PHP helper | app/Util/ |
| A post type or taxonomy slug constant | app/Constant/ |
| A page template | templates/ + views/templates/ |
| Custom Twig function or filter | app/Hook/TimberHook.php |
| An AJAX endpoint | app/Ajax/Action/ (see Ajax) |
| A REST endpoint | app/RestAPI/ (see REST API) |
| JS feature | assets/src/js/modules/ |
| SCSS component | assets/src/scss/partials/ |
Building a New Feature — Example
Section titled “Building a New Feature — Example”Here is the typical flow for adding a team members section to a page:
- Define ACF fields in
acf-php/templates/team.php - Create the PHP template in
templates/team.php— fetch posts, build context - Create the Twig template in
views/templates/team.twig— render HTML - Add SCSS in
assets/src/scss/partials/_team.scss, import it inscss/site.scss - Assign the template in WordPress admin → Page Attributes → Template
See Page Templates for the step-by-step walkthrough of this pattern.
Code Quality
Section titled “Code Quality”PHPStan
Section titled “PHPStan”Scans the PHP codebase for type errors and bugs:
ddev composer run-script phpstanPrettier
Section titled “Prettier”Formats all Twig templates:
ddev npm run prettierLinting
Section titled “Linting”Checks JS and SCSS against the project’s coding standards:
ddev npm run lintFix all errors and warnings before pushing to production. If a rule genuinely cannot be satisfied, ask a fellow developer before disabling it.
Building for production
Section titled “Building for production”Creates optimised, minified assets:
ddev npm run buildAlways verify the build succeeds before deploying.

