How PressGang compares to classic WordPress and Timber
<?php // archive-event.php
get_header(); ?>
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) :
the_post(); ?>
<h2><a href="<?php the_permalink(); ?>">
<?php the_title(); ?></a></h2>
<?php the_excerpt(); ?>
<?php endwhile; ?>
<?php endif;
get_footer();
// functions.php
add_action('init', function () {
register_post_type('event', [
'labels' => [ 'name' => 'Events' ],
'supports' => ['title', 'editor'],
'has_archive' => true,
]);
});
functions.php.<?php // archive-event.php
$context = Timber::context();
$context['events'] = Timber::get_posts([
'post_type' => 'event',
'posts_per_page' => 12,
]);
Timber::render(
'archive-event.twig',
$context
);
{# archive-event.twig #}
{% for event in events %}
<h2><a href="{{ event.link }}">
{{ event.title }}</a></h2>
{{ event.preview }}
{% endfor %}
functions.php hooks, and context management gets untidy as themes grow.// EventsController.php
// routed by convention — no stub file
class EventsController
extends AbstractController {
protected array $context_getters
= ['events'];
protected function get_events() {
return Timber::get_posts([
'post_type' => 'event',
]);
}
}
// config/custom-post-types.php
'event' => [
'supports' => ['title', 'editor'],
'has_archive' => true,
],
A framework, not a fight
Some frameworks rebuild WordPress in another framework's image — rerouted requests, reshuffled directories, magic everywhere. PressGang doesn't circumvent WordPress; it modernises it, with Timber at the core. Standard install layout, standard template hierarchy, and the classic WordPress way always one step away if you want it. If you know WordPress, you already know your way around the deck.
PageController → page.twig
PostController → single.twig
PostsController → archive.twig
SearchController → search.twig
NotFoundController → 404.twig
// extend any of them in your child theme
class PageController extends BasePageController {
// override only what you need
}
A parent theme framework
Your child theme requires PressGang via Composer and inherits a working foundation out of the box — controllers for the whole WordPress template hierarchy (pages, posts, archives, search, 404, even WooCommerce), ready to render before you write a line of code.
Everything is extensible: subclass any controller to override just what you need, and child theme config/ files merge over the parent's defaults.
Controllers as view models
Each controller gathers data via Timber in get_context(), builds a context, and selects a Twig template — a clear separation of concerns, side-effect free. Optionally, declare the template contract in $context_getters and each key wires to its getter automatically — even less boilerplate.
class EventsController extends AbstractController {
protected array $context_getters = ['events'];
protected function get_events(): iterable {
return Quartermaster::posts('event')
->whereMetaDate('start', '>=')
->orderByMeta('start', 'ASC')
->get();
}
}
// Declared, not hooked. No functions.php spaghetti.
return [
'event' => [
'labels' => [ 'name' => 'Events' ],
'supports' => ['title', 'editor', 'thumbnail'],
'has_archive' => true,
'rewrite' => [ 'slug' => 'events' ],
],
];
Config-driven bootstrapping
Post types, taxonomies, menus, blocks, scripts, styles — declared in config/ arrays, registered by convention. Inspired by Laravel: no queries, no side effects, just declarative registration you can diff and review.
Template routing — no stub files
Opt in, and requests route straight from WordPress's template hierarchy to your controllers by naming convention. Most themes need no template PHP files at all — and explicit stubs always win, so you can adopt it one deleted file at a time.
Template routing docs →front-page → FrontPageController
archive-event → EventsController
single-event → EventController
taxonomy-event-type → EventTypeController
search → SearchController
// {candidate}.twig renders when present
// One class per concern — on or off in one line
return [
'PressGang\Snippets\DisableEmojis' => [],
'PressGang\Snippets\ImageSizes' => [
'hero' => ['width' => 1920, 'crop' => true],
],
'MyAgency\Snippets\Analytics' => [
'measurement_id' => 'G-XXXXXXX',
],
];
Snippets — no more functions.php junk drawer
Each concern is its own class implementing the same clean, minimal interface, enabled or disabled with a single config line. Developers and agencies can build their own modular, reusable snippets and install them via Composer across every project — instead of relying on theme bloat or yet another plugin.
pressgang-snippets repo →·Snippet docs →Why crews choose PressGang
Modern PHP for WordPress — without abandoning WordPress. Compare it to any WordPress starter theme, Timber boilerplate, or full-stack restructure.
A fluent query builder for WordPress args
Quartermaster is a separate, standalone package — a fluent, args-first builder for WP_Query and WP_Term_Query. Build complex query arrays in readable, composable steps, then inspect the exact output with toArgs() and explain().
100% WordPress-native under the hood: plain WP args out — no ORM, no hidden query engine, no lock-in. Use it with PressGang or in any WordPress project.
$events = new WP_Query([
'post_type' => 'event',
'posts_per_page' => 12,
'meta_key' => 'start',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => [[
'key' => 'start',
'value' => wp_date('Ymd'),
'compare' => '>=',
'type' => 'DATE',
]],
]);
$events = Quartermaster::posts('event')
->paged(12)
->whereMetaDate('start', '>=')
->orderByMeta('start', 'ASC')
->wpQuery();
// Same plain WP_Query args — 5 readable steps
Your AI crew, already briefed
PressGang's best features leave barely a trace in a child theme's code — great for developers who know the ship, invisible to AI agents who don't. Bosun composes CLAUDE.md + AGENTS.md from what each theme actually has installed and enabled — so every agent that comes aboard knows the ropes.
No more hand-rolled query arrays and stub files from an unbriefed agent — working code that misses everything that makes the framework worth sailing.
Meet Bosun →$ wp package install https://github.com/pressgang-wp/pressgang-bosun.git
$ wp bosun install
✔ Inventory: pressgang 2.1 · quartermaster 1.3
✔ Template routing detected — guidance aboard
✔ Composed CLAUDE.md + AGENTS.md
✔ Skills installed → .claude/skills/
All hands briefed.
The fleet
The PressGang ecosystem — Quartermaster is fully standalone and works in any WordPress project; Capstan and Bosun enhance the PressGang developer experience.
Weigh anchor
Scaffold a full project with Capstan, or start from the pressgang-child starter theme.
$ composer global require pressgang-wp/capstan
$ wp capstan new