PHPackages                             achttienvijftien/wp-turbo-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. achttienvijftien/wp-turbo-bundle

ActiveLibrary[Framework](/categories/framework)

achttienvijftien/wp-turbo-bundle
================================

Symfony UX Turbo (Frames + Streams) for WordPress, on the 1815 service container

1.2.0(2w ago)02.9k1GPL-3.0-or-laterPHPPHP ^8.3

Since Jun 30Pushed 2w agoCompare

[ Source](https://github.com/achttienvijftien/wp-turbo-bundle)[ Packagist](https://packagist.org/packages/achttienvijftien/wp-turbo-bundle)[ RSS](/packages/achttienvijftien-wp-turbo-bundle/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (12)Versions (6)Used By (1)

wp-turbo-bundle
===============

[](#wp-turbo-bundle)

Symfony UX Turbo (Frames + Streams) for WordPress, running on `achttienvijftien/service-container`.

Registers the real `symfony/ux-turbo` and `symfony/ux-twig-component` bundles on the container and contributes the UX-specific Twig environment setup (the `` component lexer, escaper safe-classes) through the `wp_twig.configurator` tag. The generic Twig bridging (the `twig` service, `twig.extension`/`twig.runtime` tag consumption, `@BundleName` template namespaces, the Timber adapter) lives in `achttienvijftien/wp-twig-bundle`, which this package depends on.

Registers like a native Symfony bundle: list `WpTurboBundle` in the project's `config/bundles.php` (a Flex recipe writes that entry on `composer require`). The `symfony/ux-turbo` and `symfony/ux-twig-component` bundles register through their own official Symfony recipes, and `wp-twig-bundle` ships its own recipe.

This is a pure-PHP bundle (`type: library`). The frontend carrier (webroot assets, the Turbo JS runtime, asset registration) is the `achttienvijftien/wp-turbo` mu-plugin, which depends on this bundle; WordPress projects typically require that package instead of this one directly.

Frame endpoints
---------------

[](#frame-endpoints)

WordPress cedes the `/_turbo/*` namespace through one static catch-all rewrite rule (like `/wp-json` for REST); route matching happens in PHP via symfony/routing. An endpoint is a `#[Route]`-attributed service implementing the marker interface:

```
#[Route( '/_turbo/author-footer', name: 'turbo_author_footer', methods: [ 'GET' ] )]
#[WithFrameContext( CurrentPost::class )]
#[WithFrameContext( CurrentWidget::class )]
class AuthorFooterController implements TurboControllerInterface {

	public function __construct(
		private readonly FrameResponseFactory $frame_response,
		private readonly CurrentWidget $current_widget,
	) {
	}

	public function __invoke( array $params, TurboFrame $frame ): Response {
		// get_the_ID(), is_single() etc. behave as on the real post page,
		// and the widget instance's own settings are within reach.
		$settings = get_fields( 'widget_' . $this->current_widget->get_id() );

		return $this->frame_response->frame(
			'author-footer',
			/* rendered fragment */ '',
			[ 'Cache-Control' => 'public, max-age=300' ]
		);
	}
}
```

- Route `name:` is required and unique (compile-time validated); paths must live under `/_turbo/` (also compile-time validated).
- `#[WithFrameContext]` is repeatable; the declared `FrameContext` services run in declaration order before the controller and rebuild WordPress state for the request: `CurrentPost` (validates a public `post_id` and restores singular context), `CurrentWidget` (validates a `widget_id` against actively placed sidebar widgets), or your own implementation. Unknown routes, non-public posts and unplaced widgets get a controlled `text/plain` 404.
- The dispatcher only answers requests whose real path lies under `/_turbo/`, enforces the declared methods, and merges matched path placeholders over query parameters (placeholders win) for contexts and controller alike.

Building placeholders
---------------------

[](#building-placeholders)

PHP render sites (widgets) use the `FramePlaceholder` service:

```
echo $this->frame_placeholder->eager(
	frame_id: 'author-footer',
	route: 'turbo_author_footer',
	params: [ 'post_id' => $post_id ]
);
```

Use named arguments: the optional `params:`, `placeholder:` and `attributes:` arguments are then addressable by name, so you can skip the ones you don't need (here `placeholder:`) instead of threading positional defaults through.

`lazy()` exists too, but a lazy frame only loads once the element occupies space: an empty frame is a 0x0 inline element that never triggers visibility-based loading. Give it fallback content (a skeleton) or CSS dimensions first; use `eager()` otherwise.

This bundle ships no JavaScript. Placeholders fire `wp_turbo/frame_placeholder` (with the frame id), and whoever owns the Turbo runtime listens and enqueues its own script: the `achttienvijftien/wp-turbo`mu-plugin is the default carrier (composer `suggest`), a theme bundle can take over by registering its own listener. When a placeholder renders with no listener at all, the helper raises `_doing_it_wrong()` so the broken setup is loud instead of a frame that never loads.

Twig render sites author the native component, with `path()`/`url()`provided through the `UrlGeneratorInterface` contract this bundle fulfills (wp-twig-bundle registers the Twig functions when the contract is present):

```

    loading…

    fresh results

```

Note: the Stream components take a `target` prop (a full CSS selector, e.g. `#results`) and emit it as the `targets` attribute.

Controllers wrap their fragment through `FrameResponseFactory` (Turbo swaps by frame id, so the response frame must echo the placeholder's id).

`frame()` also takes an optional `attributes` array — an opaque metadata bag carried on the `Response` (the bundle stores it but never reads it). At emit time the bundle fires `wp_turbo/send_headers` (the Turbo analog of WordPress's `send_headers`), after the response's own headers are queued and before the body, so a listener can inspect the `Response` and emit further headers. Specific attribute keys are conventions agreed on by consumers, not bundle API.

Planned
-------

[](#planned)

A stream response helper for `` endpoints. Mercure/broadcast is out of scope.

Development
-----------

[](#development)

```
composer install     # the wp-twig-bundle path dist resolves against a sibling checkout
nvm use && pnpm install
pnpm wp-env start
pnpm test            # wp-env + WP test suite; clears var/cache first
```

The wp-env config maps a sibling `../wp-twig-bundle` checkout over the installed dependency inside the container.

###  Health Score

49

—

FairBetter than 94% of packages

Maintenance96

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 63.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

19d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6b266fbc7c6022e0bd39e3e1f06d75029dda33bae8d4a109d79824cb6f3bbf18?d=identicon)[dennisenderink](/maintainers/dennisenderink)

![](https://www.gravatar.com/avatar/d1030590f60d21fefa30620a1c50195fa207c5ed67f618326ba6ad781264301c?d=identicon)[omnisite](/maintainers/omnisite)

![](https://www.gravatar.com/avatar/25ddb3567dc50c66226060bf3be9ea79e580f8c16852a021f2b0c007af75d06f?d=identicon)[tmdk](/maintainers/tmdk)

---

Top Contributors

[![tmdk](https://avatars.githubusercontent.com/u/690621?v=4)](https://github.com/tmdk "tmdk (14 commits)")[![dennisenderink](https://avatars.githubusercontent.com/u/329734?v=4)](https://github.com/dennisenderink "dennisenderink (6 commits)")[![d-bunschoten](https://avatars.githubusercontent.com/u/51960786?v=4)](https://github.com/d-bunschoten "d-bunschoten (2 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/achttienvijftien-wp-turbo-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/achttienvijftien-wp-turbo-bundle/health.svg)](https://phpackages.com/packages/achttienvijftien-wp-turbo-bundle)
```

###  Alternatives

[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.2k18.1k](/packages/prestashop-prestashop)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M754](/packages/sylius-sylius)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

19566.0M1.8k](/packages/drupal-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6942.5M425](/packages/drupal-core-recommended)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M605](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
