---
title: 'Storefront: JavaScript Runtime and PluginManager | Shopware Community Hub'
description: >-
  Learn how the Shopware storefront JavaScript runtime exposes plugin APIs
  through the browser window object, how PluginManager registers plugins, and
  how async…
canonical_url: >-
  https://hub.shopware.com/learn/unit/storefront-javascript-runtime-and-pluginmanager
---

# Storefront: JavaScript Runtime and PluginManager

<LearningObjectives>

- Understand how the browser `window` object provides the runtime context for Shopware storefront JavaScript.
- Learn which Shopware-owned storefront APIs are available through `window` and how they differ from standard browser objects.
- Understand how the `PluginManager` connects plugin names, plugin classes, selectors, and matching DOM elements.
- Learn how plugin options are passed from Twig through `data-...-options` and accessed through `this.options`.
- Understand when to use `extend()` or `override()` to change an existing storefront plugin.
- Recognize async plugin registrations with `import()` and understand when the plugin code is loaded.

</LearningObjectives>

# Storefront: JavaScript Runtime and PluginManager

Storefront JavaScript runs in the browser, but Shopware does not treat it as a collection of loose scripts. Interactive behavior is organized through a runtime that exposes selected APIs through the global `window` object and connects JavaScript plugins to DOM elements.

This learning unit focuses on that runtime layer. You will first learn how browser globals work, then how Shopware extends them for the storefront, and finally how `PluginManager` registers, initializes, extends, overrides, and loads plugins.

The goal is to understand what happens before your plugin code starts running. With that mental model, registration code, selector issues, plugin options, and async loading become much easier to reason about.

## Browser Runtime: The BOM and the `window` Object

Before looking at Shopware's storefront plugin system, you need to understand one browser concept: the **Browser Object Model (BOM)**.

The BOM is a hierarchical object structure available in every web browser. It provides access to browser-specific functionality and can be extended freely, which is exactly what Shopware does.

By default, the BOM provides six key global objects. Each is responsible for a different aspect of browser functionality.

| Object      | Description                                                                             |
|-------------|-----------------------------------------------------------------------------------------|
| `window`    | Represents the browser window itself and serves as the global **root** object.          |
| `document`  | Represents the DOM of the current page.                                                 |
| `navigator` | Provides information about the browser, such as the browser name, version and language. |
| `history`   | Gives access to the browser's navigation history.                                       |
| `location`  | Contains information about the current URL (e.g., hostname, port, path name, etc.).     |
| `screen`    | Provides information about the screen size of user's browser.                           |

Every mentioned object is assigned to the `window` object. So the `window` object is the root object of the BOM.

![BOM Standard](assets/images/browser-object-model-standard.jpg)

<Callout title="Learn More About The BOM" type="info">

If you want to understand the basic concept of the BOM, check out this resource: [Browser Object Model (W3Schools)](https://www.w3schools.com/js/js_window.asp).

If you want to see what individual BOM objects provide and what you can do with them, check out this one: [Browser Objects Overview (W3Schools)](https://www.w3schools.com/js/js_ex_browser.asp).

</Callout>

### The `window` Object as the Browser Root

Let's focus on the `window` object. The `window` object represents the browser window or tab. It is automatically created every time you open a new tab or window in your browser.

The `window` object offers many properties and methods to interact with the browser environment. It is **global** and **accessible** everywhere in browser JavaScript.

The well-known DOM (Document Object Model) is also accessed through the `window` object. For example, if you write:

```js
document.getElementById('buy-box');
```

the browser resolves it through `window`:

```js
window.document.getElementById('buy-box');
```

Even though it is unnecessary to always write `window` before the `document` object, it is important to know that the `window` object is the root global object of every browser.

Shopware uses the same root object for selected storefront APIs. That means `window.PluginManager` is not a separate JavaScript world. It is a Shopware object attached to the same browser root object that also exposes `document`.

## How Shopware Extends the BOM in the Storefront

Shopware uses the browser runtime to expose important parts of the storefront JavaScript system globally.

For the storefront, Shopware adds several smaller runtime objects to `window`. Each object has a specific job in the storefront JavaScript system.

For storefront plugin development, these are the most important Shopware-owned global objects to know:

| Object                       | Role in the Storefront                                                                                  |
|------------------------------|---------------------------------------------------------------------------------------------------------|
| `window.PluginManager`       | The central registry for storefront plugins. It registers, initializes, extends, and overrides plugins. |
| `window.PluginBaseClass`     | The base class for custom storefront plugins. Your plugin class extends it.                             |
| `window.PluginConfigManager` | The manager for plugin configuration values that are read by the plugin system.                         |
| `window.Feature`             | Shopware's helper for feature flags in storefront JavaScript.                                           |

![BOM Extended: Shopware Storefront](assets/images/browser-object-model-extended-storefront.jpg)

The two most important objects for this learning unit are `window.PluginManager` and `window.PluginBaseClass`.

Every time you see code like this:

```js
PluginManager.register('StickyAddToCart', StickyAddToCartPlugin, '[data-sticky-add-to-cart]');
```

The code usually works because `PluginManager` was first read from the global `window` object:

```js
const PluginManager = window.PluginManager;

PluginManager.register('StickyAddToCart', StickyAddToCartPlugin, '[data-sticky-add-to-cart]');
```

In one line, it looks like this:

```js
window.PluginManager.register('StickyAddToCart', StickyAddToCartPlugin, '[data-sticky-add-to-cart]');
```

The same idea applies when a custom storefront plugin extends the base plugin class:

```js
const { PluginBaseClass } = window;

export default class StickyAddToCartPlugin extends PluginBaseClass {
    init() {
        // Plugin logic starts here.
    }
}
```

Which is in one line:

```js
export default class StickyAddToCartPlugin extends window.PluginBaseClass {
    init() {
        // Plugin logic starts here.
    }
}
```

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

Shopware can also make external libraries available through `window`. For example, `window.bootstrap` exposes the Bootstrap JavaScript library used by parts of the storefront. You do not need this for the basic plugin mental model, but it is useful to recognize when debugging advanced storefront behavior.

</Callout>

The mental model for the next sections is simple: the browser provides the global `window` object, and Shopware adds storefront-specific tools to it. Your plugin code uses these tools to register behavior, create plugin instances, and debug what happens on the page.

## The Storefront Plugin System in Shopware

In Shopware, interactive storefront features are not implemented as standalone scripts. Instead, Shopware uses a plugin system that attaches JavaScript behavior directly to DOM elements.

This means that storefront functionality is structured around plugins and plugin instances, rather than global scripts running across the entire page.

### Mental Model: Plugins Are Attached to DOM Elements

The central idea is simple: a plugin class describes behavior, and a selector decides where that behavior should be used.

For example, if a plugin is registered for `[data-buy-box]`, Shopware looks for matching elements in the DOM and creates plugin instances for them.

### Key Terms

To understand how the system works, it is helpful to distinguish a few important terms:

- **Plugin**: A JavaScript class that is usually attached to a DOM element and controls behavior for that element.
- **Plugin instance**: The concrete object created for one specific element (e.g., one buy box on one PDP).
- **Registration**: Connecting a plugin name and a selector with a plugin class via `PluginManager`.
- **Async plugin**: A plugin that is loaded via `import()` only when needed.
- **`this.el`**: The DOM element a plugin instance is attached to. For element-bound plugins, this is the matched element. For global plugins, there is no feature-specific matched element, so you should not design your plugin around `this.el` as a local component root.

### What Happens During Plugin Initialization

When Shopware initializes the storefront, the plugin system performs several steps:

- It searches the DOM for elements that match registered selectors (e.g., `[data-buy-box]`, `.buy-box`, `#buy-box`).

If no element matches the registered selector, Shopware skips the plugin instance for that selector. The rest of the storefront JavaScript continues to run normally.

- It creates one plugin instance for each matching element.
- It calls `init()` when the plugin instance is created for the first time. This method is typically the starting point of a storefront plugin.
- It calls `update()` when plugins are re-initialized on the same element instead of creating a new instance.

### Element-Bound Plugins vs. Global Plugins

If a plugin is registered without a selector, the plugin will be initialized globally instead of being attached to specific DOM elements.

For element-bound plugins, `this.el` is the matched DOM element. For example, if a plugin is registered for `[data-buy-box]`, `this.el` is the buy box element for that specific instance.

For global plugins, there is no feature-specific matched element. Use global plugins only for behavior that really belongs to the page as a whole. If your logic controls a specific UI area, prefer an element-bound plugin so `this.el` gives you a stable local root for DOM queries.

### Re-Initialization and Dynamic DOM Updates

Re-initialization usually happens when parts of the storefront are updated dynamically, for example, through AJAX interactions (e.g., listing filters or off-canvas cart) or when plugins trigger it with `window.PluginManager.initializePlugins()`.

A full page reload does not trigger `update()`. Instead, all plugins start again with `init()`.

The plugin system itself only manages plugin initialization and lifecycle. Therefore, keep in mind the following:

- Business logic should stay in the backend whenever possible.
- You need to avoid duplicate event listeners when plugins are initialized again.
- The plugin system does not guarantee that your plugin works on every DOM change; use the lifecycle to refresh state when needed.

Handling these aspects is the responsibility of the plugin implementation.

### Where Plugin Instances Live

Shopware plugin system saves plugin instances on the DOM element itself. This means that a DOM element can hold one or more plugin instances.

Because of this, plugin instances can also be inspected directly in the browser during debugging:

**Example:**

```js
const buyBoxEl = document.querySelector('[data-buy-box]');
const buyBoxInstance = window.PluginManager.getPluginInstanceFromElement(buyBoxEl, 'BuyBox');
```

This is extremely useful when you want to inspect state (`instance.options`, `instance.el`) and reproduce issues.

If you are not sure which plugin instances exist on an element, inspect all instances attached to that element:

```js
const buyBoxEl = document.querySelector('[data-buy-box]');
const instances = window.PluginManager.getPluginInstancesFromElement(buyBoxEl);
```

If you need all instances of a registered plugin across the page, use the plugin name:

```js
const cartWidgets = window.PluginManager.getPluginInstances('CartWidget');
```

### Passing Options from Twig to Storefront Plugins

In addition to lifecycle and registration, storefront plugins often require configuration values, for example:

- A threshold (`400px` scroll)
- A selector (`.buy-box`)
- A feature flag (`true/false`)

In Shopware, these values are usually passed from Twig into the HTML using data attributes.

The plugin system automatically reads these attributes and merges them with the plugin's `static options`. Inside the storefront plugin, the final values are available via `this.options`.

**Real-world example:** Configure the scroll threshold of a sticky add-to-cart bar from Twig.

```twig
{# Twig: attach plugin + pass options as JSON #}
<div
    data-sticky-add-to-cart="true"
    data-sticky-add-to-cart-options='{"threshold": 400}'
></div>
```

The naming is intentional:

- `data-sticky-add-to-cart` is the selector hook. It tells the `PluginManager`: "Create a `StickyAddToCart` instance for this element."
- `data-sticky-add-to-cart-options` contains JSON configuration for that plugin instance.

In real templates, the JSON is often created with `json_encode` and rendered into an HTML attribute. Because quotes and special characters can break the surrounding attribute, escape the encoded value for the HTML attribute context, for example with `escape('html_attr')`.

Do not read too much into the value `true`. The plugin system mainly needs the attribute to exist and match the selector. The options attribute is separate because it transports configuration, not the activation marker itself.

The same pattern exists in Shopware core. For example, a plugin can be bound to `[data-buy-box]`, while the configuration is passed through `data-buy-box-options`.

```js
import Plugin from 'src/plugin-system/plugin.class';

export default class StickyAddToCartPlugin extends Plugin {
    static options = {
        threshold: 400,
    };

    init() {
        const threshold = this.options.threshold;
        // Use the threshold in your logic (for example in a scroll handler).
    }
}
```

The important idea is: PHP/Twig decides the values, the HTML transports them, and the plugin reads them via `this.options`.

This creates a simple one-way bridge from PHP/Twig to the storefront JavaScript (not the other way around).

In many cases, values from plugin configuration (`config.xml`) are also passed this way. Twig reads the configuration (e.g., via `config('YourPlugin.config.someValue')`) and passes it through `data-...-options` to the storefront plugin.

## The PluginManager

The central component of Shopware's storefront plugin system is the `PluginManager`. Shopware makes the `PluginManager` available via the global `window` object, which is the browser's global runtime object.

```js
window.PluginManager;
```

This makes the plugin system available throughout the storefront runtime.

The `PluginManager` is responsible for managing storefront plugins. Its main responsibilities include:

- Registering plugins.
- Finding matching DOM elements.
- Creating plugin instances.
- Managing the plugin lifecycle (`init()` and `update()`).

This means, the `PluginManager` acts as the orchestrator of all storefront plugins.

Whenever the storefront initializes, the `PluginManager` scans the DOM for registered selectors and creates the corresponding plugin instances.

### What Happens on the Initial Page Load

When the storefront loads for the first time, the storefront JavaScript bundle registers all available plugins (many of them asynchronously).

Then, after the DOM has finished loading (`DOMContentLoaded`), Shopware triggers the plugin initialization:

```js
// This is handled by Shopware internally.
// You do NOT need to implement this yourself.
document.addEventListener('DOMContentLoaded', () => {
    window.PluginManager.initializePlugins();
}, false);
```

During `initializePlugins()`, the `PluginManager` performs several steps:

- It loads async plugins that were registered with `() => import(...)`.
- It scans the current DOM for elements that match registered selectors (for example `[data-buy-box]` on the PDP).
- It creates plugin instances and calls `init()` once per instance.
- It calls `update()` if an instance already exists and plugins are initialized again.

Important: Registering a plugin is different from initializing it. Registration only tells the `PluginManager` which plugins exist. Actual plugin instances are created when `PluginManager.initializePlugins()` (or `initializePlugin()`) runs.

Because the `PluginManager` is attached to the global `window` object, plugin entry files (`main.js`) can access it and register storefront plugins using `PluginManager.register()`.

For convenience, it is usual to assign it to a local variable:

```js
const PluginManager = window.PluginManager;
```

This avoids repeatedly accessing the global `window` object.

## Registering and Deregistering Plugins

After understanding how the plugin system works internally, the next step is understanding how plugins become part of the system.

This happens through registration. When a plugin is registered, the `PluginManager` knows which plugin class should be used and which DOM elements should receive that behavior (respectively globally).

### Register a Plugin

Storefront plugins are registered using `PluginManager.register()`:

```js
import StickyAddToCartPlugin from 'src/plugin/sticky-add-to-cart/sticky-add-to-cart.plugin';

const PluginManager = window.PluginManager;

PluginManager.register('StickyAddToCart', StickyAddToCartPlugin, '[data-sticky-add-to-cart]');
```

The important idea is: **registration is configuration**. It tells Shopware which element should get which behavior.

### Deregister a Plugin

If you need to remove a plugin registration (for example, in a feature flag setup, or during testing), you can deregister it:

```js
window.PluginManager.deregister('StickyAddToCart');
```

## Extending vs. Overriding Plugins

Shopware gives you two main strategies to change existing storefront plugin behavior:

- **Override**: Replace the original plugin class with your own implementation.
- **Extend**: Add or replace selected methods while keeping the original plugin as the base.

Choosing the right strategy is important for maintainability. As a general rule:

- Use `extend()` if you only need to modify or enhance parts of the original behavior.
- Use `override()` if you need full control over the plugin implementation.

### Override

The `override` approach gives you full control over the plugin behavior, but it also means that you become responsible for keeping your implementation compatible with future Shopware updates.

`override()` is the right choice when you need full control over the plugin behavior, and you are ready to own the full implementation:

### Extend (Patch Specific Methods)

`extend()` allows you to modify specific parts of a plugin while keeping the original implementation as the base. This is usually the safer option when you only need small adjustments.

In many real-world projects, extending a plugin is preferred because it reduces the risk of breaking changes when Shopware updates the original plugin.

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

Use `extend()` for small, targeted changes. If you find yourself rewriting many methods, an override is usually a clearer and safer decision.

</Callout>

## Async Plugin Loading and Asynchronous JavaScript

Async plugin loading is especially useful for features that are only needed on specific pages or sections of the storefront.

### Why Shopware Uses Async Plugins

Modern storefronts register many plugins, but only some of them are needed on every page.

To reduce initial JavaScript bundle size and speed up the first render, Shopware often registers plugins asynchronously using `import()`:

```js
window.PluginManager.register(
    'BuyBox',
    () => import('src/plugin/buy-box/buy-box.plugin'),
    '[data-buy-box]'
);
```

This uses JavaScript's dynamic import, which allows the browser to load the plugin module only when it is necessary.

During `PluginManager.initializePlugins()`, Shopware detects the async registration, loads the module, and then creates the plugin instance.

This is important for performance because it keeps page-specific JavaScript out of the initial storefront bundle. For example, a buy box plugin is only useful on pages that actually contain a buy box. A category listing, a CMS landing page, or an account page should not have to download and parse that code during the first page load if the matching selector is not present.

### What This Means for Your Plugin Code

Even if your plugin is loaded asynchronously, the plugin class itself behaves the same way as a synchronous plugin. The lifecycle methods (`init()` and `update()`) are executed in the same way.

Your main responsibility is to keep the `init()` method lightweight and avoid heavy work during initialization. Expensive operations should only run when necessary (e.g., after user interaction or when an element becomes visible).

## Summary

In this learning unit, you learned:

- How the BOM and the global `window` object provide the browser runtime for storefront JavaScript.
- Which Shopware-owned storefront globals are most relevant for plugin development.
- How Shopware’s `PluginManager` connects plugin classes, selectors, and DOM elements.
- How plugin options travel from Twig through `data-...-options` into `this.options`.
- When to use `register()`, `deregister()`, `extend()`, and `override()`.
- How async plugin loading works with `import()`.

With this knowledge, you can read storefront plugin registration code and understand how Shopware turns it into running plugin instances. The next learning unit builds on this foundation and focuses on plugin structure, lifecycle patterns, event binding, and debugging.
