Shopware 6 Hidden Gems #12: StorefrontRenderEvent — one subscriber, variables in every template
Fabian Blechschmidt
„I need this variable in every storefront template.“ Every Shopware project arrives at this sentence eventually — a trust badge config, a feature toggle for the frontend, an A/B test bucket, some ERP status. And the solutions I’ve seen in the wild are consistently worse than they need to be: Twig extensions with service lookups, decorated page loaders, or — the classic — abusing a header/footer pageloaded subscriber and hoping every relevant page actually loads the header.
Meanwhile, the actual answer sits in plain sight in StorefrontController::renderStorefront() (Controller/StorefrontController.php:74-76). Every single storefront render — every controller, every page, every AJAX HTML response that goes through renderStorefront() — dispatches an event first:
$event = new StorefrontRenderEvent($view, $parameters, $request, $salesChannelContext);
$this->container->get('event_dispatcher')->dispatch($event);
The docs don’t mention it anywhere. Let’s fix that.
The event
Storefront/Event/StorefrontRenderEvent.php gives you, per render:
getView() — the template about to be rendered (@Storefront/storefront/page/product-detail/index.html.twig, …)
getParameters() / getParameter($key) / setParameter($key, $value) — the exact variable bag Twig will receive
That last method is the gem. Whatever you setParameter() here is a top-level variable in the rendered template. No page struct extension, no Twig global gymnastics.
The five-minute version
class TrustBadgeSubscriber implements EventSubscriberInterface
{
public function __construct(private readonly SystemConfigService $config) {}
public static function getSubscribedEvents(): array
{
return [StorefrontRenderEvent::class => 'onRender'];
}
public function onRender(StorefrontRenderEvent $event): void
{
$event->setParameter('acmeTrustBadge', $this->config->get(
'AcmePlugin.config.trustBadge',
$event->getSalesChannelContext()->getSalesChannelId()
));
}
}
Done. Every template, every page type, sales-channel-aware.
The core shows how to use it properly
This isn’t some forgotten side door — it’s how Shopware itself injects its global template data. TemplateDataSubscriber (storefront/Framework/Routing/TemplateDataSubscriber.php) hangs three listeners on this one event and provides variables you have definitely used without asking where they come from:
hrefLang — the alternate-language links in your page head
appShopId — the shop id for app iframes
themeIconConfig — the icon set configuration of the active theme
Worth reading as a pattern catalog, too: it pulls the route name from $event->getRequest()->attributes->get('_route') to decide what to compute — which brings us to targeting.
Targeting: not every page, just some
Because you get the view name and the request, the „global“ event does selective work well:
public function onRender(StorefrontRenderEvent $event): void
{
// only on checkout pages
if (!str_starts_with((string) $event->getRequest()->attributes->get('_route'), 'frontend.checkout')) {
return;
}
$event->setParameter('acmeCheckoutHints', $this->hintLoader->load(
$event->getSalesChannelContext()
));
}
You can also read parameters before the template does — e.g. peek at the page object a controller passed and derive something from it. And since the event is dispatched before Twig starts, this is a legitimate interception point for A/B testing: put the bucket decision into a parameter and let templates branch on it, one subscriber for the whole experiment.
What it is not
Two honest caveats. First, the event fires per renderStorefrontController call — expensive work in your listener is paid on every page load. Cache accordingly (the trust badge above: fine; a DAL query: think twice, or lazy-load in the template only where used). Second, it won’t help outside renderStorefront() — raw Symfony controllers or the Store API obviously never dispatch it.
And yes — you could solve most of this with a Twig extension exposing a service function. The difference is philosophical but real: the event puts data into the template scope, computed once, visible in the profiler’s template variables; the Twig function hides a service call inside the template, invisible until it’s slow.
TL;DR
Every storefront render dispatches StorefrontRenderEvent before Twig runs. Subscribe, call $event->setParameter('myVar', $value), and myVar exists in every template — sales-channel-aware, route-targetable via $event->getRequest(), same mechanism the core uses for hrefLang and friends (TemplateDataSubscriber). The cleanest „global template variable“ mechanism in Shopware, and the docs never mention it.
Next up: the search tokenizer — why shopware.search.preserved_chars decides whether your customers can find AB-123.4, and how to tune it in two lines of YAML.
What did you inject globally? Drop me an email!
Found in Shopware 6.7.0.0: dispatch in storefront/Controller/StorefrontController.php:74-76, event API in storefront/Event/StorefrontRenderEvent.php (setParameter, line 72), core usage in storefront/Framework/Routing/TemplateDataSubscriber.php:33-40.