---
title: 'Storefront: Twig Patterns and Context Handling | Shopware Community Hub'
description: >-
  Explore Twig patterns and Shopware-specific Twig helpers to build robust and
  maintainable storefront templates.
canonical_url: >-
  https://hub.shopware.com/learn/unit/storefront-twig-patterns-and-context-handling
---

# Storefront: Twig Patterns and Context Handling

<LearningObjectives>

- Understand Shopware-specific Twig helpers and when to use them.
- Apply defensive Twig patterns to prevent runtime errors in storefront templates.
- Work confidently with the `page`, `context`, and other commonly used storefront objects.
- Structure Twig overrides in a maintainable and update-safe way.

</LearningObjectives>

# Storefront: Twig Patterns and Context Handling

In real-world storefront projects, Twig templates often need to handle optional data, route context, and Shopware-specific integrations.

In this learning unit, you will learn how to use advanced Twig features, apply defensive template patterns, understand the structure of the `page` and `context` objects, and learn when (and when not) to use the `app` Object.

## Shopware-Specific Twig Helpers

Shopware extends Twig with custom features that are commonly used in storefront templates.

Unlike native Twig features, these helpers are tightly integrated with Shopware's routing, media handling, configuration system, and multi-inheritance mechanism.

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

Shopware provides `sw_*` wrappers for several Twig features to support **multi inheritance** in the storefront (e.g., `sw_extends`, `sw_include`, `sw_embed`, `sw_use`, `sw_import`, `sw_from`, `sw_block`, `sw_source`).

</Callout>

### The `seoUrl` Extension

The `seoUrl` function generates SEO-friendly URLs for routes and entities. It ensures that links follow Shopware's SEO routing logic and remain consistent with the configured URL structure.

This is important because you usually do not want to hardcode technical routes like `/detail/[id]`. Instead, Shopware can generate localized, sales-channel-aware SEO paths and keep them consistent when SEO settings or templates change.

**Typical use cases:**

- Linking to **product detail pages** in listings, cross-sellings, or CMS blocks.
- Linking to **category pages** (navigation, breadcrumbs).
- Linking to other frontend routes where Shopware provides SEO mappings.

**Example:**

```twig
<a href="{{ seoUrl('frontend.detail.page', { productId: page.product.id }) }}">
  {{ page.product.translated.name }}
</a>
```

This generates the correct SEO URL for the current product detail route.

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

In Storefront templates, using SEO URLs is the best practice because it keeps links consistent with Shopware's SEO configuration and helps search engines discover and index your pages. For internal navigation, you technically can link to technical routes, but in most storefront templates you should rely on SEO URLs.

</Callout>

### The `rawUrl` Extension

The `rawUrl` function generates a **fully qualified URL** (including scheme and host) for a given route.

**Typical use cases:**

- Generating links in **emails** (where a relative URL is often not enough).
- Generating absolute links for external systems, exports, feeds, or tracking.
- Creating canonical or absolute links when required.

```twig
<a href="{{ rawUrl('frontend.account.login.page') }}">
  Login
</a>
```

### The `theme_config` Function

You already know the `config` function, which reads system-wide or plugin configuration values.

In addition, Shopware provides a `theme_config` function, which reads values from the active theme configuration.

While theme configuration is often used in SCSS, it can also influence markup or runtime behavior in Twig.

The `theme_config()` function returns the **resolved value** of the active theme config. The data type depends on the field:

- **String**: Common for text fields and URLs.
- **Number**: Common for breakpoints (like `breakpoint.md`).
- **Boolean**: Common for switches / checkboxes.
- **Array**: Used for lists (for example, compiled CSS assets like `assets.css`).

Even when a field is defined as `"type": "media"` in `theme.json`, `theme_config()` returns a **string URL/path** in Twig (Shopware resolves media IDs to URLs).

**Typical use cases:**

- Toggling features or layout variants.
- Adding CSS classes or data attributes based on theme settings.
- Switching templates or partials based on theme configuration.
- Reading theme-specific configuration values such as logo URLs or media references.

**Example: Render a theme logo (media field)**

```twig
{# Media config values resolve to a URL/path string in Twig. #}
{% set desktopLogoUrl = theme_config('sw-logo-desktop') %}

{% if desktopLogoUrl %}
  <img src="{{ desktopLogoUrl|sw_encode_url }}"
       alt="{{ 'header.logoLink'|trans|striptags }}"
       class="header-logo-image"
  />
{% endif %}
```

The `sw_encode_url` filter URL-encodes path segments (e.g., spaces or special characters) so the browser can load the file reliably.

**Example: Use breakpoints in a responsive `<picture>`**

A common real-world pattern is to load different assets depending on the screen size.

Instead of hardcoding breakpoint values, you can use the theme configuration to stay aligned with the active theme.

```twig
{% set mobileLogo = theme_config('sw-logo-mobile') %}
{% set desktopLogo = theme_config('sw-logo-desktop') %}
{% set md = theme_config('breakpoint.md') %}

<picture>
  {% if mobileLogo %}
    <source srcset="{{ mobileLogo|sw_encode_url }}"
            media="(max-width: {{ md - 1 }}px)">
  {% endif %}

  {% if desktopLogo %}
    <img src="{{ desktopLogo|sw_encode_url }}"
         alt="{{ 'header.logoLink'|trans|striptags }}">
  {% endif %}
</picture>
```

In Twig, you usually do not "detect the viewport" directly. Instead, you use CSS media queries or a picture element and align them with the theme using `theme_config('breakpoint.*')`.

Avoid hardcoding breakpoint values. Use `theme_config('breakpoint.*')` to keep your templates consistent with the active theme.

<Callout title="Note: Breakpoints vs. sw_thumbnails" type="info">

The `sw_thumbnails` is a Twig tag for **media entities** (for example, product images). It renders an image element with `srcset` and `sizes`. The browser then picks the best image for the current viewport.

This is different from `<picture>`, where you explicitly define breakpoints in markup using `media="()"`.

**Typical use cases**

- Use **`sw_thumbnails`** when you render images from the Shopware media system (product images, CMS images). You want responsive loading (`srcset`/`sizes`) and good performance without manually writing the HTML.
- Use **`<picture>`** when you want to switch between different files depending on the breakpoint (for example a mobile logo vs. a desktop logo) or when your image URLs come from config values like `theme_config()`.

</Callout>

<Callout title="Reminder: theme.json Field Definition vs. Twig Value" type="info">

In `theme.json`, each config field is a small definition object (metadata + value). Common keys are:

- Mandatory: `type`, `value`, `editable`
- Optional: `label`, `helpText`, `tab`, `block`, `section`, `order`
- Optional: `scss` (set to `false` if the field should not become an SCSS variable)
- Optional: `fullWidth` (UI hint in the theme manager)

In Twig, `theme_config()` gives you only the resolved value (string/number/boolean/array).

</Callout>

### The `searchMedia` Extension

The `searchMedia` function resolves one or multiple media IDs to media objects. It's especially useful when working with custom entities or fields that store only media IDs (useful for galleries or sliders).

This allows you to resolve media directly in Twig without manually querying a repository.

```twig
{# Example: resolve a MediaEntity from a Media ID #}
{% set mediaId = page.product.cover.mediaId|default(null) %}

{% if mediaId is not same as(null) %}
    {% set coverMediaCollection = searchMedia([mediaId], context.context) %}
    {% set coverMedia = coverMediaCollection.get(mediaId) %}

    {% if coverMedia %}
        <img src="{{ coverMedia.url }}" alt="{{ coverMedia.alt|default('') }}">
    {% endif %}
{% endif %}
```

### The `sw_source` Function

The `sw_source` function is a Shopware-specific wrapper for the native Twig `source` function. It returns the raw content of a **template file** instead of rendering it, with support for Shopware's multi inheritance.

**Typical use cases:**

- Inline custom SVGs that are stored in your theme/plugin `Resources/views/` (so they are accessible via the Twig loader)
- Debugging template contents (for dev environments)

```twig
{# Example: inline a custom SVG that is available via the Twig loader (Resources/views/) #}
{{ sw_source('@MyTheme/storefront/assets/icons/black_friday.svg') }}
```

**`sw_icon` vs `sw_source`:**

- Use **`sw_icon`** when you want to render icons from an icon set (consistent styling and easy replacement).
- Use **`sw_source`** when you want to inline the raw markup of a custom SVG file you control.

Use `sw_source` carefully in production, as inlining large SVG files may increase HTML size.

### The `sw_block` Function

The `sw_block` function is a Shopware-specific wrapper for the native Twig `block` function. It supports multi inheritance.

It allows you to render a block multiple times or render it inside another template.

```twig
{{ sw_block('component_product_box') }}
```

In its simplest form, this renders a block from the current template context. If you want to render a block from another template file, pass the template name as the second argument (same API as Twig's `block()`):

```twig
{{ sw_block('my_block_name', '@Storefront/storefront/layout/footer/footer.html.twig') }}
```

```twig
{% block my_block %}
    <p>Hello World</p>
    {{ sw_block('component_product_box') }}
{% endblock %}
```

### The `sw_embed` Tag

The `sw_embed` tag is a Shopware-specific wrapper for the native Twig `embed` tag. This is a powerful tag that allows you to include a template file in another template and override included blocks. It also supports multi inheritance.

This is useful when:

- A template is reused in multiple places (e.g., alerts or product cards).
- You need to override specific blocks only in a certain context.
- You want to keep the original structure but adjust parts of it.

**Example: Customize an alert locally**

```twig
{% sw_embed '@Storefront/storefront/utilities/alert.html.twig' with {
  type: 'info',
  heading: 'Heads up',
  content: 'This is the default alert content.'
} %}
  {# Override a block to customize the content #}
  {% block utilities_alert_content %}
    {{ parent() }}

    <p><strong>Extra tip:</strong> You can add additional markup here.</p>
  {% endblock %}
{% end_sw_embed %}
```

In this example:

- The alert template is reused.
- Only the `utilities_alert_content` block is extended.
- **Other alerts in the storefront are not affected, although they use the same base template.**

The key idea of `sw_embed` is to create a local variation of a template. It does not replace the original template but only modifies the embedded version inside a specific block.

**Example: Adjust the product card only in the category listing**

Product cards are used in many places: Category listings, cross-selling on the product detail page, CMS elements, search results, and more.

Sometimes, you want to slightly change it only in one context, for example, in the category listing.

```twig
{# Place this embed inside your category listing template. #}
{# This example assumes `product`, `element`, and `page` are available in this context. #}

{% if product is defined and element is defined and page is defined %}
  {% set sizes = { xs: '500px', sm: '315px', md: '390px', lg: '350px', xl: '280px' } %}

  {% sw_embed '@Storefront/storefront/component/product/card/box-standard.html.twig' with {
    product: product,
    element: element,
    page: page,
    layout: 'standard',
    sizes: sizes
  } %}
    {% block component_product_box_name %}
      {{ parent() }}

      <span class="badge bg-warning text-dark">Special Listing Label</span>
    {% endblock %}
  {% end_sw_embed %}
{% endif %}
```

In this example:

- The product card template is reused as usual.
- Only the product name block is extended.
- The change only applies where this `sw_embed` code is placed (the category listing template).
- Other usages of the product card (e.g., cross-selling on the product detail page) are not affected.

In most projects, you first extend the **category listing template** with `sw_extends`. This is the place where you control how product cards are rendered in the listing.

At this point, you usually have two ways to add a small change (like a badge next to the product name):

**Option A: Global override (affects many places)**
If you override the product card template itself (for example `box-standard.html.twig`) using `sw_extends`, the change can affect **every place** where this card is used (category listing, cross-selling, CMS elements, search results, etc.). Use this approach only if you want the same change everywhere.

**Option B: Local change (only this place)**
If you want the change only in the category listing, use `sw_embed` inside your listing template. The `sw_embed` keeps the original product card as the base template but lets you extend only the necessary block for this single usage.

This means the change applies only to the category listing and other usages of the product card remain unchanged.

**In short:**

- Use `sw_extends` on the product card when you want a **global** change.
- Use `sw_embed` when you want a **local** change in one specific context.

#### `sw_extends` vs. `sw_embed`

Both allow block overrides, but they serve different purposes:

- **`sw_extends`**:
   - Create a new file
   - Override behavior globally
   - Used for theme/plugin customization
- **`sw_embed`**:
   - Used directly inside a template
   - Override behavior locally
   - Used for small, contextual changes

**When to use what?**

- Use `sw_extends` for permanent overrides (theme/plugin level).
- Use `sw_embed` for small adjustments inside a template.
- Use `sw_include` if you just want to include a template without changes.

This powerful tag saves a lot of time and keeps template structures clean, especially when working with reusable components.

---

If you want a full overview of what Shopware provides, see: [Shopware's Twig functions](https://developer.shopware.com/docs/resources/references/storefront-reference/twig-function-reference.html).

## Further Twig Features

This section focuses on writing robust and defensive templates. In real storefront projects, not every variable exists on every page type. These features help you avoid runtime errors and keep your templates stable.

### The `default` Filter

You already heard the `default` filter from the previous learning path. Here, we use it defensively to provide safe fallback values when optional data is missing or empty.

**Typical use cases:**

- Provide a safe fallback for optional page data (e.g., meta title, teaser, custom fields).
- Avoid empty headings or labels in the UI.
- Ensure safe rendering when values may be missing (not defined) or `null`.

```twig
{{ page.metaInformation.metaTitle|default('Shopware Storefront') }}
```

### The `defined` Test

Use `is defined` before accessing properties that may not exist on every page type. Accessing a non-existing property without guarding it may cause a runtime error.

**Typical use cases:**

- Guard page-specific data (e.g., `page.product` only exists on product detail pages).
- Check if an extension/association was loaded.
- Avoid "variable/attribute does not exist" errors in Twig.

```twig
{% if page.product is defined %}
    <h1>{{ page.product.translated.name }}</h1>
{% endif %}
```

### The `apply` Tag

The `apply` tag lets you apply a filter to an entire block of content instead of wrapping every single expression.

**Typical use cases:**

- Apply filters to a snippet of markup.
- Keep templates readable when you need the same transformation multiple times.

```twig
{% apply upper %}
    This text becomes uppercase
{% endapply %}
```

You can use any filter that exists in Twig/Shopware to filter entire blocks of markup.

For example, you can also chain filters and pass arguments:

```twig
{% apply lower|escape('html') %}
    <strong>SOME TEXT</strong>
{% endapply %}
{# outputs "&lt;strong&gt;some text&lt;/strong&gt;" #}
```

### Built-In Whitespace Control

Older Twig code sometimes used the `spaceless` filter (often with `apply`) to remove whitespace between HTML tags.

This approach is deprecated in modern Twig. Instead, use Twig's built-in whitespace control with `-`. The `-` trims whitespace directly around the Twig tag where it is used, including:

- Line breaks
- Indentation
- Spaces before or after a Twig expression or control tag

This is mainly useful when:

- Trimming unwanted line breaks around Twig output.
- Avoiding unwanted whitespace in inline elements.
- Keeping loops and small fragments from adding extra empty lines.

#### Whitespace Around Expressions

Without whitespace control:

```twig
{% set label = 'Sale' %}

<span class="badge">

  {{ label }}

</span>
```

Rendered HTML:

```html
<span class="badge">

        Sale

    </span>
```

When inspected in the browser, the text is still displayed as `Sale`. The important difference is in the generated HTML: The element contains extra line breaks and indentation around the text.

With whitespace control:

```twig
{% set label = 'Sale' %}

<span class="badge">

  {{- label -}}


</span>
```

Rendered HTML:

```html
<span class="badge">Sale</span>
```

The `-` after the opening expression delimiter removes whitespace before the expression. The `-` before the closing expression delimiter removes whitespace after the expression.

#### Whitespace Around Control Tags

Whitespace control also works with Twig control tags such as `if`, `for`, or `set`.

Without whitespace control:

```twig
<ul>
  {% for category in categories %}
    <li>{{ category.name }}</li>
  {% endfor %}
</ul>
```

Rendered HTML:

```html
<ul>
                    <li>Clothing</li>
                    <li>Shoes</li>
            </ul>
```

When inspected in the browser, the list still looks correct. The difference is that the generated HTML contains line breaks and indentation around the list items.

With whitespace control:

```twig
<ul>
  {%- for category in categories -%}
    <li>{{ category.name }}</li>
  {%- endfor -%}
</ul>
```

Rendered HTML:

```html
<ul><li>Clothing</li><li>Shoes</li></ul>
```

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

Whitespace control only affects whitespace directly around the Twig tag where `-` is used.

It does not automatically minify the entire HTML document or fix spacing for you. Use it deliberately where the surrounding whitespace is actually part of the problem.

</Callout>

### The `json_encode` Filter

When you need to pass data from Twig to JavaScript, `json_encode` ensures safe serialization.

**Typical use cases:**

- Provide structured configuration via `data-*` attributes.
- Passing initial state for storefront JavaScript plugins.
- Passing plugin configuration or other shop-specific data to storefront JavaScript.
- Creating structured analytics or tracking payloads.

You can think of `json_encode` as a bridge between Twig and JavaScript, allowing you to safely transfer backend data (available in Twig) to frontend (storefront JavaScript).

```twig
{% set tracking = {
    productId: page.product.id|default(null),
    productName: page.product.translated.name|default(null)
} %}

<div data-tracking='{{ tracking|json_encode }}'></div>
```

### The `iterable` Test

Use `is iterable` before looping when you are not sure whether a value is a collection.

**Typical use cases:**

- Looping optional CMS structures.
- Looping associations or extensions that may be `null`.
- Avoiding runtime errors when a value is not a list or collection.

```twig
{% if page.cmsPage is defined and page.cmsPage.sections is iterable %}
    {% for section in page.cmsPage.sections %}
        {# Some Content #}
    {% endfor %}
{% endif %}
```

---

Of course, Twig provides more features. For a full list, see the [official documentation](https://twig.symfony.com/doc/3.x/).

## Context Data Access

In Shopware storefront templates, you typically work with a few well-known variables. Which ones are available depends on the current page type and on which template or partial is being rendered.

### The `page` Object

The `page` object is the primary object in most storefront views. It contains page-specific data, for example:

- **Product detail page (PDP)**: `page.product`
- **Listing/category page**: `page.listing`
- **Cart/checkout page**: `page.cart`

**Typical use cases:**

- Accessing the entity of the current page (e.g., `page.product`).
- Rendering page-specific components (listing, cart, CMS page).
- Understanding what data is available for a specific route.

In development, use `dump()` to explore what the current `page` contains.

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

In reusable partials, `page` might not always be available. Guard it with `is defined` if you are unsure.

</Callout>

### The `context` Object

The `context` object represents the **SalesChannelContext**. It contains information such as language, currency, customer state, shipping/payment methods, and more.

**Typical use cases:**

- Check whether a customer is logged in: `context.customer`.
- Display currency or language-dependent information: `context.currency`, `context.language`.
- Render sales-channel specific information: `context.salesChannel`.

A common pattern is to read the customer from the context:

```twig
{% set customer = context.customer|default(null) %}
{% if customer %}
    <p>{{ customer.firstName }} {{ customer.lastName }}</p>
{% endif %}
```

### The `customer` Object

The `customer` object represents the currently logged-in customer (if any). It typically contains data such as:

- The `firstName`, `lastName`, `email`.
- Billing/shipping addresses (depending on what was loaded).
- Custom fields and customer-specific metadata.

**Where you usually access it:**

- In most storefront pages via `context.customer` (recommended and widely available).
- On account/checkout related pages it may also be available as `customer`, depending on the template.

```twig
{% set customer = context.customer|default(null) %}
{% if customer %}
    <p>Welcome back, {{ customer.firstName }}!</p>
{% else %}
    <a href="{{ rawUrl('frontend.account.login.page') }}">Login</a>
{% endif %}
```

### The `product` Object

The `product` object represents a product entity. Typical properties you work with in Twig are:

- The `translated.name` and `translated.description`.
- The `id` and `productNumber`.
- The `price`, `calculatedPrice` (depending on context).
- Media and cover information (depending on what was loaded).

**Where you usually access it:**

- On the product detail page via `page.product`.
- In listings via `page.listing` (products are usually in a result/collection, not a single `product`).
- In partials/components, `product` is often passed explicitly (e.g., product card).

```twig
{% if page.product is defined %}
    <h1>{{ page.product.translated.name }}</h1>
{% endif %}
```

**Example (listing, simplified)**:

```twig
{% if page.listing is defined and page.listing is iterable %}
  {# page.listing is a search result / collection that you can iterate over #}
  {% for product in page.listing %}
    <a href="{{ seoUrl('frontend.detail.page', { productId: product.id }) }}">
      {{ product.translated.name }}
    </a>
  {% endfor %}
{% endif %}
```

### The `order` Object

The `order` object represents an order entity. It can contain data such as:

- Order number and order date/time.
- Order customer data.
- Addresses and deliveries.
- Transactions and payment/shipping state.
- Line items (products, promotions, shipping).

**Where you usually access it:**

- In checkout/order confirmation-related templates usually via `page.order` (depending on the route and what the page provides).
- In emails, where an order is commonly passed directly as `order`.

```twig
{% if page.order is defined %}
    <p>Order number: {{ page.order.orderNumber }}</p>
{% endif %}
```

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

Variables like `customer`, `product`, or `order` are not guaranteed globals. In storefront pages you will typically find them via `page` or `context` (e.g. `page.product`, `context.customer`, `page.order`) or they are passed explicitly into a partial.

</Callout>

### The `app` Object

The `app` object is a Symfony/Twig global (provided by Symfony's Twig bridge). In the storefront, it is mainly useful for accessing **request-related information** via `app.request`.

Use it for small UI decisions but avoid implementing business logic in Twig.

**Typical use cases:**

- Checking the current route to render small variations in markup.
- Reading query parameters for UI concerns (e.g., active tab).
- Quick debugging in development (`dump(app.request)`).

**Examples:**

Route-based markup variations (e.g., minimal header in checkout):

```twig
{% set route = app.request.attributes.get('_route')|default('') %}

{% if route starts with 'frontend.checkout' %}
    {# checkout-specific markup #}
{% endif %}
```

Reading query parameters for UI concerns (e.g., active tab):

```twig
{% set activeTab = app.request.query.get('tab')|default('overview') %}
{% if activeTab is same as('reviews') %}
  {# reviews tab markup #}
{% endif %}
```

Generating a route-based CSS hook for scoping styles:

```twig
{% set route = app.request.attributes.get('_route')|default('') %}
<div class="page-wrapper route-{{ route|replace({'.': '-'}) }}">
  {# Some Content #}
</div>
```

### When (And When Not) to Use `app`

Shopware already exposes route-related variables like `activeRoute` / `activeRouteParameters` in many core templates. Prefer those when available, and use `app.request` mainly when you explicitly need request-level data.

Twig is meant for presentation logic. Business logic should be implemented in PHP.

**Do NOT use `app` for the following:**

- Implementing business logic (price rules, permission decisions, etc.)
- Accessing customer/authentication data as your primary source of truth. Preferring `context.customer` is recommended.
- Building complex conditionals logic based on request internals.

If you need complex decisions, compute the final state in PHP (page loader, subscriber, controller) and pass the prepared data into the template.

## Summary

In this learning unit, you deepened your understanding of Twig usage in the storefront. You learned how to:

- Use Shopware-specific Twig helpers such as `seoUrl`, `rawUrl`, `theme_config`, `searchMedia`, `sw_block` and `sw_embed`.
- Apply defensive Twig patterns such as `default`, `defined`, `is iterable`, `apply`, and `json_encode` to build robust templates.
- Safely transfer data from Twig to JavaScript via `json_encode`.
- Understand how data is exposed common storefront variables such as `page`, `context`, `customer`, `product`, `order`, and `app`.
- Recognize the proper role of Twig as a presentation layer and avoid implementing business logic inside templates.

With this knowledge, you can build robust, maintainable, and update-safe storefront templates in Shopware.
