6.4 (current)

App Scripts

App Scripts allow your app to include logic that is executed inside the Shopware execution stack. It allows you to build richer extensions that integrate more deeply with Shopware.
Note that app scripts were introduced in Shopware, and are not supported in previous versions.

Script Hooks

The entry point for each script are the so-called "Hooks". You can register one or more scripts inside your app, that should be executed whenever a specific hook is triggered. Through the hook your script gets access to the data of the current execution context and can react to or manipulate the data in some way.
See the Hooks reference for a complete list of all available.


At the core App Scripts are twig files that are executed in a sandboxed environment. Based on which hook the script is registered to, the script has access to the data of that hook, and pre-defined services, that can be used to execute your custom logic.
Apps scripts are placed in the Resources/scripts directory of your app. For each hook you want to execute a script on, create a new subdirectory. The name of the subdirectory needs to match the name of the hook.
You can place one or more .twig files inside each of these subdirectories, that will be executed when the hook gets triggered.
The file structure of your apps should look like this:
└── DemoApp
├── Resources
│ └── scripts // all scripts are stored in this folder
│ ├── product-page-loaded // each script in this folder will be executed when the `product-page-loaded` hook is triggered
│ │ └── my-first-script.twig
│ ├── cart
│ │ ├── first-cart-script.twig
│ │ └── second-cart-script.twig // you can execute multiple scripts per hook
│ └── ...
└── manifest.xml

Including scripts

Sometimes scripts can become more complex or you want to extract common functionaltiy. Thus it is handy to split your scripts into smaller parts that can later be included in other scripts.
In order to do that you can compose your reusable scripts into twig macros, put them inside a dedicated include folder and then import them using the twig import functionality.
└── DemoApp
├── Resources
│ └── scripts
│ ├── include
│ │ └── media-repository.twig // this script may be included into the other scripts
│ ├── cart
│ │ ├── first-cart-script.twig
│ └── ...
└── manifest.xml
Note that app scripts can use the return keyword to return values to the caller.
A basic example may look like this:
{% macro getById(mediaId) %}
{% set criteria = {
'ids': [ mediaId ]
} %}
{% return services.repository.search('media', criteria).first %}
{% endmacro %}
{% import "include/media-repository.twig" as mediaRepository %}
{% set mediaEntity = mediaRepository.getById(myMediaId) %}

Interface Hooks

Some "Hooks" describe interfaces this means that your scripts for that hook need to implement one or more functions. E.g. the store-api-hook defines a cache_key and a response function. Those functions are closely related, but are executed separately. To implement the different functions, you use different twig blocks with the name of the function:
{% block cache_key %}
// provide a cacheKey for the incoming request
{% endblock %}
{% block response %}
// produce the response for the request
{% endblock %}
Some functions are optional whereas others are required, in the above example the cache_key function is optional. That means you can omit that block in your script without an error (but caching for the endpoint won't work in that case). The response function is required, which means that if your script does not provide a response block it will lead to an error.
Note that for each function you get access to different input data or services, so in the cache_key block you don't necessarily have access to the same data and services as in the response block. The available data and services are described for each hook (or each function in InterfaceHooks) in the reference documentation.


Inside the app script you have access to the storefront translation mechanism, by using the |trans-filter.
{% set translated = 'my.snippet.key'|trans %}
{% do call.something('my.snippet.key'|trans) %}

Extended syntax

In addition to the default twig syntax, app scripts can also use a more PHP-flavoured syntax.

Equals check with ===

Instead of using the rather verbose `
` you can use the more dense `===` equality checks.
{% if var === 1 %}
{% endif %}
Additionally, you can also use the !== not equals operator as well.
{% if var !== 1 %}
{% endif %}

Loops with foreach

Instead of the for...in syntax for loops you can also use a foreach tag.
{% foreach list as entry %}
{{ entry }}
{% break %}
{% endforeach %}

Instance of checks with is

You can use a is check to check the type of a variable.
{% if var is string %}
{% endif %}
The following types are supported:
  • true
  • false
  • boolean / bool
  • string
  • scalar
  • object
  • integer / int
  • float
  • callable
  • array

Type casts with intval

You can cast variables into different types with the intval filter.
{% if '5'|intval === 5 %}
{# always evaluates to true #}
{% endif %}
The following type casts are supported:
  • intval
  • strval
  • boolval
  • floatval

conditions with && and ||

Instead of using AND or OR in if-conditions, you can use the && or || shorthands.
{% if condition === true && condition2 === true %}
{% endif %}

return tag

You can use the return tag to return values from inside macros.
{% macro foo() %}
{% return 'bar' %}
{% endmacro %}

Available services

Depending on the hook that triggered the execution of you script you get access to different services, you can use inside your scripts e.g. to access data inside Shopware or to manipulate the cart. Take a look at the hook reference to get a complete list of all available services per hook.
Additionally, we added a ServiceStubs-class, that can be used as typehint in your script, so you get auto-completion features of your IDE.
{# @var services \Shopware\Core\Framework\Script\ServiceStubs #}
{% set configValue = services.config.app('my-app-config') %}
The stub class contains all services, but depending on the hook not all of them are available.

Example Script - loading media entities

Assuming your app adds a custom field set for the product entity with a custom media entity select field.
When you want to display the file of the media entity in the storefront, it is not easily possible, because in the template's data you only get the id of the media entity, but not the url of the media file itself.
For this case you can add an app script on the product-page-loaded-hook, that loads the media entity by id and adds it to the page object, so the data is available in templates.
{# @var services \Shopware\Core\Framework\Script\ServiceStubs #}
{% set page = hook.page %}
{# @var page \Shopware\Storefront\Page\Product\ProductPage #}
{% if page.product.customFields.myCustomMediaField is not defined %}
{% return %}
{% endif %}
{% set criteria = {
'ids': [ page.product.customFields.myCustomMediaField ]
} %}
{% set media = services.repository.search('media', criteria).first %}
{% do page.addExtension('swagMyCustomMediaField', media) %}
For a more detailed example on how to load additional data, please refer to the data loading guide.
Alternatively take a look at the cart manipulation guide to get an in-depth explanation on how to manipulate the cart with scripts.

Developing / Debugging Scripts

You can get information about what scripts were triggered on a specific storefront page inside the Symfony debug toolbar.
The debug toolbar is only visible if your Shopware installation is in APP_ENV = dev, please ensure you set the correct env - e.g. in your .env file - when developing app scripts .
You can find all hooks that are triggered and the scripts that are executed for each by clicking on the script icon.
Symfony Debug Toolbar
That will open the Symfony profiler in the script detail view, where you can see all triggered hooks and the count of the scripts that were executed for each script at the top.
Script Debug Toolbar
Additionally, you can use the debug.dump() function inside your scripts to dump data to the debug view. A script like this:
{% do debug.dump(hook.page) %}
Will dump the page object to the debug view.
Output of debug.dump()