---
title: 'Storefront: Extending Twig and SCSS Configuration | Shopware Community Hub'
description: >-
  Learn how to create custom Twig extensions and inject SCSS variables into
  Shopware’s theme compilation.
canonical_url: >-
  https://hub.shopware.com/learn/unit/storefront-extending-twig-and-scss-configuration
---

# Storefront: Extending Twig and SCSS Configuration

<LearningObjectives>

- Create and register custom Twig extensions (functions and filters) in a Shopware plugin.
- Apply best practices when implementing Twig helpers and avoid common anti-patterns.
- Understand the difference between compile-time and runtime in the storefront context, including the storefront build process.
- Inject SCSS variables into theme compilation using an event subscriber and know when this approach is appropriate.

</LearningObjectives>

# Storefront: Extending Twig and SCSS Configuration

In many storefront projects, you need small template helpers or configuration-based styling that are not provided by default.

Shopware offers dedicated extension points that allow you to implement these features in a clean and maintainable way.

This learning unit introduces two common extension mechanisms:

- **Twig extensions** for template helpers
- **Theme compiler SCSS variables** via a subscriber

The goal is to keep templates clean, keep logic testable, and keep theme compilation predictable.

## Choosing the Right Extension Point

Before writing code, decide which layer should solve the problem.

- Use a **Twig extension** when the template needs a small helper for data that is already available during rendering.
- Use **theme configuration** when merchants should control styling values, such as colors or spacing, through the administration.
- Use a **theme compiler subscriber** when a value must be converted into an SCSS variable during compilation, for example from plugin configuration or system configuration.
- Use the **backend** when data must be loaded, permissions must be checked, or business rules must be evaluated before rendering.
- Use the **storefront build process** to compile SCSS and JavaScript and prepare assets for the browser.

The short version: Twig is for rendering and small helpers around already prepared data. Business logic belongs in the backend.

## Creating a Twig Extension

Twig extensions are typically placed under the `[your_plugin]/src/Twig` folder.

```txt
[shop_root]
 └── custom
     └── plugins
         └── [your_plugin]
              └── src
                  │── Resources
                  │   │── app
                  │   │   │── administration
                  │   │   │   └── ...
                  │   │   └── storefront
                  │   │       └── ...                                   
                  │   └── views   
                  └── Twig
                      │── MyTwigFunctions.php 
                      │── MyTwigFilters.php 
                      └── ... 
```

You may separate Twig extensions into multiple files per usage type (functions and filters), or combine them into a single extension class.

Twig extension classes must extend `Twig\Extension\AbstractExtension`:

```php
<?php declare(strict_types=1);

namespace Swag\MyPlugin\Twig;

use Twig\Extension\AbstractExtension;

class MyTwigFunctions extends AbstractExtension
{ }
```

And register the class as a service and tag it with `twig.extension`:

```xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
  <services>
    <service id="Swag\MyPlugin\Twig\MyTwigFunctions">
      <tag name="twig.extension"/>
    </service>
  </services>
</container>
```

## Twig: Custom Functions

Custom Twig functions are useful when you want a standalone helper that reads nicely in templates (e.g., capability checks in B2B storefronts).

The following example assumes that the relevant customer data is already available on the customer entity. The Twig function only reads that data and returns a simple boolean.

### Example: Customer Capability Checks (B2B Rendering)

```php
<?php declare(strict_types=1);

namespace Swag\MyPlugin\Twig;

use Shopware\Core\Checkout\Customer\CustomerEntity;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class CustomerCapabilityTwigExtension extends AbstractExtension
{
    public function getFunctions(): array
    {
        return [
            new TwigFunction('swag_customer_can_order_on_invoice', [$this, 'customerCanOrderOnInvoice']),
            new TwigFunction('swag_customer_has_permission', [$this, 'customerHasPermission']),
        ];
    }

    public function customerCanOrderOnInvoice(?CustomerEntity $customerEntity): bool
    {
        if (null === $customerEntity) {
            return false;
        }

        // Example only: decide based on a customer custom field
        $customFields = $customerEntity->getCustomFields() ?? [];

        return (bool) ($customFields['swag_can_order_on_invoice'] ?? false);
    }

    public function customerHasPermission(?CustomerEntity $customerEntity, string $permission): bool
    {
        if (null === $customerEntity) {
            return false;
        }

        // Example only: assume an array custom field like ['bulk-order', 'quick-order']
        $customFields = $customerEntity->getCustomFields() ?? [];
        $permissions = $customFields['swag_permissions'] ?? [];

        return \is_array($permissions) && \in_array($permission, $permissions, true);
    }
}
```

**Usage in Twig**

```twig
{% if swag_customer_can_order_on_invoice(context.customer) %}
  {# show "invoice" option #}
{% endif %}

{% if swag_customer_has_permission(context.customer, 'bulk-order') %}
  {# show bulk order UI #}
{% endif %}
```

<Callout title="Tip" type="info">

Use functions like these only for **fast checks** on already available data (e.g., customer entity, custom fields, precomputed permissions). **Avoid doing database queries during template rendering.**

</Callout>

<Callout title="Cache and Security" type="warning">

Be careful with customer-specific output in cached storefront areas such as headers, navigation, listings, or shared CMS content. A Twig helper can make a template easier to read, but it does not automatically make customer-specific rendering safe.

For security-relevant permissions, pricing decisions, or sensitive customer data, prepare the result in the backend. Then pass a simple, cache-aware value to Twig.

</Callout>

## Twig: Custom Filters

Twig filters transform a value. They are a good fit for formatting, masking, and debug output.

### Example: Masking / Privacy Filter (Email)

```php
<?php declare(strict_types=1);

namespace Swag\MyPlugin\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class DebugAndPrivacyTwigExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('swag_mask_email', [$this, 'maskEmail']),
            new TwigFilter('swag_pretty_json', [$this, 'prettyJson']),
        ];
    }

    public function maskEmail(?string $email): string
    {
        if (true === empty($email) || false === str_contains($email, '@')) {
            return '';
        }

        [$local, $domain] = explode('@', $email, 2);
        $visible = mb_substr($local, 0, 2);

        return $visible . '***@' . $domain;
    }

    public function prettyJson(mixed $value): string
    {
        $json = json_encode($value, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);

        return $json === false ? '' : $json;
    }
}
```

**Usage in Twig**

```twig
{{ customer.email|swag_mask_email }}
{# Output example: jo***@example.com #}
```

<Callout title="Important" type="warning">

Only use pretty JSON output in non-production environments (and avoid exposing sensitive data).

</Callout>

## Twig Extension Responsibilities and Best Practices

Twig extensions are used to expose small, reusable helper functions to templates.

**Typical responsibilities include**:

- **Formatting helpers**: Transform values for presentation (e.g., masking personal data, formatting identifiers, etc.).
- **Context checks**: Expose information about the current context (e.g., whether a customer is logged in, etc.).
- **Feature toggles**: Provide configuration-based switches that allow templates to conditionally render content.
- **Integration helpers**: Offer lightweight utilities (e.g., resolving URLs, checking asset availability, etc.).

**Avoid the following**:

- **Don’t do database queries** inside Twig functions. Prepare the data in the backend before rendering.
- **Don’t do heavy computations** (loops over large collections, complex mapping) inside Twig.
- **Don’t hide side effects** (writes, tracking calls, external HTTP calls) behind a template helper.
- **Don’t re-implement business logic** in the storefront layer.

If a decision needs database access, permission checks, pricing rules, customer segmentation, or external services, calculate the result in the backend before Twig renders the page. Twig should receive a simple value and render it.

**Rule of thumb**: Twig extensions should be **cheap** (fast), **pure** (no side effects), and **easy to cache**.

### Quick Reference: Compile-Time, Runtime, and the Build Process

Understanding the difference between **compile-time**, **runtime**, and the **storefront build process**, is important when working with Twig extensions and SCSS variables.

**Compile-Time**

Happens during the storefront build and theme compilation steps (e.g., the project script `bin/build-storefront.sh`, its Shopware CLI equivalent `shopware-cli project storefront-build`, and `bin/console theme:compile`).

At this stage, SCSS is compiled into CSS — SCSS variables (`$...`), mixins, and SCSS `@if` are evaluated.

**Runtime**

When a page is requested and displayed, it has three parts:

- **Server-side runtime (PHP)**: Shopware handles the request, loads data (DAL), runs business logic (controllers/page loaders/subscribers) and prepares the `page` object.
- **Twig runtime (PHP)**: Twig renders the final HTML and executes Twig functions/filters while rendering.
- **Browser runtime**: CSS is applied (including CSS variables `--...`) and JavaScript runs for interactions.

In short: Compile-time prepares assets before a page request is rendered, while runtime is what happens when a request is processed and a page is rendered.

**Storefront Build Process**

The classic project script `bin/build-storefront.sh` typically runs these steps (simplified). In Shopware CLI based workflows, the equivalent command is `shopware-cli project storefront-build`.

1. **Build storefront bundles** (JavaScript/CSS) into `dist/`.
2. **Install and copy assets** into the public directory (`bin/console assets:install`).
3. **Compile the theme** for active themes (`bin/console theme:compile --active-only`).
4. Optionally **clear caches** (`bin/console cache:clear`).

## Injecting SCSS Variables via an Event Subscriber

Sometimes you need to inject additional SCSS variables during theme compilation—most commonly when a value comes from **plugin configuration** and should be applied **per sales channel** (for example a color-picker value).

Shopware provides the event `ThemeCompilerEnrichScssVariablesEvent` for this purpose.

### Step 1: Provide a Default in SCSS (`!default`)

Always provide a fallback value in SCSS, so your theme still compiles even if the subscriber is not active.

```scss
// app/storefront/src/scss/base.scss
$swag-myplugin-banner-background-color: #0055ff !default;
```

### Step 2: Add the Variable in a Subscriber

```php
<?php declare(strict_types=1);

namespace Swag\MyPlugin\Subscriber;

use Shopware\Storefront\Theme\Event\ThemeCompilerEnrichScssVariablesEvent;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ThemeScssVariableSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly SystemConfigService $pluginConfig
    ) {
    }

    public static function getSubscribedEvents(): array
    {
        return [
            ThemeCompilerEnrichScssVariablesEvent::class => 'onAddScssVariablesEvent',
        ];
    }

    public function onAddScssVariablesEvent(ThemeCompilerEnrichScssVariablesEvent $event): void
    {
        // Example: Inject a value based on a plugin configuration (color-picker),
        // resolved per sales channel via $event->getSalesChannelId()
        $event->addVariable(
            'swag-myplugin-banner-background-color', 
            $this->pluginConfig->get('SwagMyPlugin.config.bannerBackgroundColor', $event->getSalesChannelId()) ?? '#ffcc00'
        );
    }
}
```

Ensure your color-picker configuration has a default value.

**Plugin Configuration** (`<plugin root>/src/Resources/config/config.xml`)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">

    <card>
        <title>Example configuration</title>
        <input-field type="colorpicker">
            <name>bannerBackgroundColor</name>
            <label>Banner background color</label>
            <defaultValue>#ffcc00</defaultValue> <!-- Default value -->
        </input-field>
    </card>
</config>
```

Register the subscriber (`plugin root/src/Resources/config/services.xml`)

```xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
  <services>
    <service id="Swag\MyPlugin\Subscriber\ThemeScssVariableSubscriber">
      <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>
      <tag name="kernel.event_subscriber"/>
    </service>
  </services>
</container>
```

### Step 3: Use the Variable in SCSS

```scss
.my-plugin-banner {
  background-color: $swag-myplugin-banner-background-color;
}
```

### When NOT to Use a Subscriber

Use `ThemeCompilerEnrichScssVariablesEvent` intentionally. It is powerful, but it also adds another “moving part” to theme compilation.

- **If the value is a normal theme setting**: prefer the theme configuration system (`theme.json` / `config.xml`) so merchants can change it in the Administration and you don’t need custom subscriber logic.
- **If you only need a per-component variant** (e.g.,F one button should have different padding): prefer **CSS variables** scoped to a selector (see the previous learning unit). This keeps the customization local and easier to debug in DevTools.
- **If the value does not depend on runtime configuration**: prefer plain SCSS with `!default` and regular imports. A subscriber is most useful when you must turn *config/service values* into SCSS variables during compilation.

## Troubleshooting Checklist

- **Twig function not available**: Ensure the service is tagged with `twig.extension` and caches are cleared.
- **SCSS variable isn't applied**: Ensure you defined a `!default` fallback and recompiled the theme after changes.
- **Changes don’t show up**: Clear cache (`bin/console cache:clear`) and compile the theme (`bin/console theme:compile`).

## Summary

In this learning unit, you learned:

- Create and register Twig extensions for reusable storefront helper functions.
- Apply best practices when implementing Twig functions and filters.
- The difference between compile-time and runtime and the storefront build process.
- Inject SCSS variables into Shopware's theme compilation using `ThemeCompilerEnrichScssVariablesEvent`.

With this knowledge, you can safely extend the storefront with template helpers and configuration-based styling, while keeping your templates clean, your logic easy to maintain and your theme build predictable.

---

Congratulations! By completing this learning unit, you successfully finished this course. You now understand how to customize the Shopware storefront using themes, Twig templates, SCSS styling, and extension points.
