Almost every page on a site needs a handful of the same values: the site’s contact email, the global navigation items, the full list of services. Without Composers you’d repeat the same database calls in every controller method and risk them going stale independently. Composers solve this by declaring what data belongs to which views in a single place, then letting the framework merge that data automatically before any controller response is serialised — whether that’s an HTML shell for first load or a JSON payload for SPA navigation.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Ahondev/portfolio-v2/llms.txt
Use this file to discover all available pages before exploring further.
How Composers work
Composers run onwp_loaded, before any routing decision is made. ComposerManager::registerComposers() scans app/Composers/, instantiates every class it finds, calls views() to discover which views the composer targets, calls compose() to get the data, and stores it all in an internal registry keyed by view name.
When WebController::view() builds a response, it calls ComposerManager::getView($view), which merges:
- All data registered under the wildcard key
'*' - All data registered for the specific view name
$data array before the final $__wp_data__ payload is assembled.
The ComposerInterface
Every Composer must implement two methods:
views(): array
Return an array of view name strings. The special value '*' makes the composer inject its data into every view response.
compose(): array
Return an associative array of key-value pairs. Every key becomes a top-level property in the view’s data object.
Creating a Composer
Create the class file
Add a PHP file in
app/Composers/. The filename must match the class name. The class should extend Base (for access to remember() and service()) and implement ComposerInterface.The AppComposer — real example
This is the global composer that runs on every view in the portfolio. It injects the site contact email and the full list of services (cached via a transient so the DB is only hit once every five minutes):
site_email and site_services in its data block — no controller changes required.
Wildcard vs. targeted Composers
views() return value | Behaviour |
|---|---|
['*'] | Data is injected into all views. Use for truly global data like nav items, site settings, or authentication state. |
['home', 'agence'] | Data is injected only when view() is called with one of those exact view names. Use for data that is expensive to compute and only relevant to a subset of pages. |
Accessing Composer data in React
Because Composer data is merged into$data before the $__wp_data__ payload is assembled, it appears inside the data key of window.__wp_data__ on first load, and inside the data key of the JSON response on SPA navigation.
On the TypeScript side, use the useWPData() hook to access the full payload:
Because Composers run on every request, keep
compose() lean. Use remember() (inherited from Base) to cache any query that hits the database, especially queries shared between multiple Composers or controllers.Registering multiple Composers
Drop any number of class files intoapp/Composers/ — each one is auto-discovered. A useful pattern is to have one global Composer for site-wide data and one per page section for heavier queries:
Base, implement ComposerInterface, return data from compose(). The framework handles the rest.