21.08.2026・TechStuff
21.08.2026・TechStuff

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:

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()
        ));
    }
}
{% if acmeTrustBadge %}
    {% sw_include '@AcmePlugin/storefront/component/trust-badge.html.twig' %}
{% endif %}

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:

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.