---
title: 'Storefront: Core Features | Shopware Community Hub'
description: >-
  Learn which Shopware core storefront helpers, utilities, and plugins you can
  reuse, and how to use them safely in your custom code.
canonical_url: 'https://hub.shopware.com/learn/unit/storefront-core-features'
---

# Storefront: Core Features

<LearningObjectives>

- Learn the difference between core storefront plugins, helpers, and services/utilities.
- Use selected core helpers and utilities in your own storefront plugin.
- Apply common UX safeguards for async interactions, such as loading indicators and duplicate-action prevention.
- Understand the difference between importing stable core helpers and copying core storefront code.
- Debug common storefront plugin issues using typical patterns and console checks.

</LearningObjectives>

# Storefront: Core Features

Working with storefront JavaScript is not only writing custom code. A big part of real-world development is knowing what Shopware already provides and how to reuse it.

Shopware offers many core building blocks, such as storefront plugins, helpers and services/utilities. Understanding these components helps you avoid reinventing solutions and makes your code more maintainable and easier to debug.

In this learning unit, you will get a structured overview of these building blocks.

## Mental Model: Where Core Storefront Features Live

Think of Shopware storefront JavaScript as three layers:

- **Core plugins**: Feature implementations attached to DOM elements (e.g., add-to-cart, listing filters, search suggestions).
- **Core helpers**: Small, reusable tools you import into your plugins.
- **Core services and utilities**: Shared infrastructure code, used by plugins and sometimes useful for your custom plugins.

As a developer, you usually do one of these things:

- Use a helper or service in your storefront plugin (recommended for common tasks).
- Extend or override a core plugin if you need to change existing behavior (only when needed).

## Storefront Helpers

Shopware provides a number of helpers that you can use in your plugins. Helpers are small reusable functionalities and functions that solve common problems in storefront development, such as event handling, DOM updates, or responsive behavior. This means you do not have to implement these solutions from scratch.

### `Debouncer`: Prevent Too Many Calls

Use a debouncer when an event is triggered very often (typing, resize, scroll). It delays the execution until the user stops triggering the event.

**Example:**

```js
import Debouncer from 'src/helper/debouncer.helper';

const { PluginBaseClass } = window;

export default class QuickSearchPlugin extends PluginBaseClass {
    init() {
        const input = this.el.querySelector('input[type="search"]');

        input.addEventListener(
            'input', 
            Debouncer.debounce((event) => {
                this._search(event.target.value);
            }, 250)
        );
    }

    _search(query) {
        // Do your request here.
    }
}
```

Debouncing can have side effects. It waits before it runs your code. This is great to reduce requests, but it can also make the UI feel “late” if the delay is too high or your logic is heavy.

Use these rules of thumb:

- If an event can fire 10–100 times per second, debounce it.
- Keep the delay small (often **150–300 ms** for search inputs).
- Do not debounce actions that must feel instant (for example, button clicks that submit a form).

### `StringHelper`: Convert Names and Parse Values

`StringHelper` is a small collection of string utilities. Two common use cases in storefront code are:

- Converting names between formats (camelCase ↔ dash-case).
- Parsing primitive values when you receive them as strings.

```js
import StringHelper from 'src/helper/string.helper';

// Example: convert a JS key into a data-attribute style key
StringHelper.toDashCase('stickyAddToCart'); // "sticky-add-to-cart"

// Example: parse a primitive from a string (JSON, numbers, booleans)
StringHelper.parsePrimitive('true');   // true
StringHelper.parsePrimitive('42');     // 42
StringHelper.parsePrimitive('3,14');   // 3.14
```

The last example is a technical normalization: if a value looks like a simple decimal number, Shopware replaces the comma with a dot before parsing it as JSON. This is useful for primitive values that come from HTML attributes, but it is **not** a full locale-aware number parser. Do not use it for formatted prices, measurements, or user-facing number formats.

### `DeviceDetection`: Adapt Behavior for Touch Devices

If your UI relies on hover or precise pointer interactions, you often need to adjust behavior for touch devices.

```js
import DeviceDetection from 'src/helper/device-detection.helper';

const { PluginBaseClass } = window;

export default class HoverPreviewPlugin extends PluginBaseClass {
    init() {
        if (DeviceDetection.isTouchDevice()) {
            // On touch devices, hover UX often feels broken.
            return;
        }

        this.el.addEventListener('mouseenter', () => this._openPreview());
    }

    _openPreview() {
        // ...
    }
}
```

The `DeviceDetection` provides a consistent way to detect touch devices across your project. Internally, it uses a mix of capability checks (e.g., touch support) and browser detection via `navigator.userAgent`.

Typical use case: Disable hover-based interactions (e.g., previews or tooltips) on touch devices where no real hover exists.

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

Avoid building core business logic on device detection. This helper is mainly useful for UI adjustments and edge cases.

Also, keep in mind that checks (e.g., IE or old Edge deletion) are mostly relevant for legacy setups.

</Callout>

### `ViewportDetection`: React to Screen Size Changes

If your behavior depends on the screen size (mobile vs. desktop), you need a way to react when the breakpoints change.

`ViewportDetection` reads the active Bootstrap breakpoint from CSS and notifies you when it changes.

Typical use cases:

- Enable features only on desktop.
- Disable heavy interactions on mobile
- Re-initialize logic when the viewport changes.

```js
import ViewportDetection from 'src/helper/viewport-detection.helper';

const { PluginBaseClass } = window;

export default class ResponsiveHintPlugin extends PluginBaseClass {
    init() {
        this._viewportDetection = new ViewportDetection();

        document.$emitter.subscribe('Viewport/hasChanged', (event) => {
            const { previousViewport } = event.detail;
            // React to breakpoint changes here.
        });
    }
}
```

If you only need a one-time check (no events), you can also read the current breakpoint directly:

```js
if (ViewportDetection.isLG() || ViewportDetection.isXL() || ViewportDetection.isXXL()) {
    // Desktop-only behavior
}
```

**Real-world examples:**

In real storefront projects, breakpoint changes often require UI updates:

- Navigation: Desktop layouts vs. mobile off-canvas. On breakpoint change, you may need to close or reset UI state.
- Sliders vs. grids: A carousel on mobile can become a grid on desktop (or the other way around). You may need to re-initialize the UI.
- Heavy interactions: Image zoom, sticky sidebars, or hover previews can be enabled on desktop and disabled on mobile for UX and performance reasons.
- Filters: Listing filters can be a sidebar on desktop, but an off-canvas on mobile. Breakpoint changes can require different focus or scroll handling.

<Callout title="Why Not Only CSS/Bootstrap?" type="info">

CSS (and Bootstrap) is great for layout and styling. But it cannot manage complex JavaScript behavior. When the breakpoint changes, you often need to:

- Add or remove event listeners (hover vs. click).
- Remove or clean up and re-initialize UI components (e.g., a slider instance).
- Reset UI state (close an off-canvas, clear an “active” item).

`ViewportDetection` helps you react to these changes in a consistent way.

</Callout>

#### Excursus: The `$emitter`

In the Shopware storefront, `document.$emitter` is a simple way to send and listen to events between plugins.

You can think of it like this:

- One plugin publishes an event (sends a message) when something happens.
- Another plugin subscribes (listens) and reacts to it.

This allows plugins to communicate without directly depending on each other.

**Important:** Data is passed via `event.detail`.

```js
// Publisher (somewhere in your code)
document.$emitter.publish('Wishlist/added', { productId: '...' });

// Subscriber (in another plugin)
document.$emitter.subscribe('Wishlist/added', (event) => {
    const { productId } = event.detail;
    // React here
});
```

In addition, each plugin instance also has its own emitter: `this.$emitter`.

This emitter is scoped to the plugin element (`this.el`). This means only code that has access to this specific plugin instance can subscribe to these events. It is **not** globally available like `document.$emitter`.

```js
// Publisher (inside a plugin instance)
this.$emitter.publish('MyPlugin/ready');

// Subscriber (when you already have the instance)
const el = document.querySelector('[data-my-plugin]');
const instance = window.PluginManager.getPluginInstanceFromElement(el, 'MyPlugin');

instance.$emitter.subscribe('MyPlugin/ready', () => {
    // React here
});
```

<Callout title="When to Use It (and When Not)" type="neutral">

Use `$emitter` for cross-plugin UI signals (e.g., off-canvas closed, item added).

Do not use it as a global state store. If two plugins are always used together, direct method calls can be simpler.

</Callout>

### `ElementReplaceHelper`: Replace DOM Snippets From HTML Responses

When you fetch server-rendered HTML (e.g., from a widget endpoint), you often want to replace only certain parts of the page without reloading everything.

`ElementReplaceHelper` helps with exactly that: It takes an HTML string (or DOM) and replaces the **inner HTML** of matching elements in the current page.

This is important to understand:

- It does **not** replace the whole element node. It replaces `target.innerHTML`.
- That means old DOM inside the target is removed. Any event listeners inside that DOM are gone too.

If you already have exactly the HTML you want for one specific element, using `innerHTML` directly is totally fine:

```js
const el = document.querySelector('[data-cart-widget]');
el.innerHTML = html;
```

The value of `ElementReplaceHelper` is that you can extract and replace **only specific parts** from a larger HTML response (and you can update multiple selectors at once) without manually parsing the markup yourself.

This is useful when your response contains multiple fragments (e.g., header, cart, off-canvas, etc.).

```js
import ElementReplaceHelper from 'src/helper/element-replace.helper';

// Replace only the cart widget and the off-canvas cart snippet (example selectors).
ElementReplaceHelper.replaceFromMarkup(responseHtml, [
    '[data-cart-widget]',
    '[data-offcanvas-cart]',
]);
```

#### Practical Example: Replace a Header Counter After an AJAX Call

Imagine you request a small widget endpoint that returns updated header HTML (e.g., cart count). You can replace the matching DOM parts and then re-initialize plugins if needed.

```js
const response = await fetch('/widgets/checkout/info', {
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
});

const html = await response.text();

ElementReplaceHelper.replaceFromMarkup(html, [
    '[data-cart-widget]',
]);

// If your updated HTML contains plugin markup, scan again:
window.PluginManager.initializePlugins();
```

**Explanation**

The first argument (`html`) is the **source** (the fetched HTML string). Shopware parses it into a temporary document.

The second argument is a list of **CSS selectors**. For each selector, Shopware:

- Finds the matching element(s) in the source HTML.
- Finds the matching element(s) on the **current page** (the real browser DOM, `document`).
- Copies the source `innerHTML` into the matching element(s) on the current page.

Use selectors that are stable across themes and template changes. Data attributes like `data-...` are often a good choice. Avoid selectors that depend on long class name chains.

<Callout title="Common Pitfall: “My Click Handler Stopped Working”" type="warning">

Because `innerHTML` is replaced, the old DOM nodes are removed. Any event listeners attached to those nodes are removed as well. If you need a click handler, attach it via a plugin so it can be re-initialized or use event delegation.

</Callout>

### `Feature`: Guard Code With Feature Flags

Feature flags allow you to enable or disable behavior without changing or removing code.

In Shopware, feature flags can be exposed on `window.features`. This is a Shopware core mechanism (not a built-in JavaScript feature). The `Feature` helper provides a small API to check if a flag is active.

```js
import Feature from 'src/helper/feature.helper';

if (Feature.isActive('FEATURE_NEXT_12345')) {
    // Run the new behavior only when the flag is enabled.
}
```

Typical use cases:

- Gradually roll out new features.
- Keep old and new behavior in parallel during refactoring.
- Enable features only in specific environments (e.g., staging vs. production).

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

Feature flags are mainly used in core development and larger projects.

In most custom plugins, you will use them less often. But you may encounter them when working with Shopware core code or extensions.

If you need feature toggles in a custom plugin, you often use plugin configuration (`config.xml`) and pass values into the storefront via `data-...-options` instead.

</Callout>

### `FocusHandler`: Preserve Focus for Modals and Off-Canvas UI

When opening modals or off-canvas panels, the browser focus often gets lost. This can lead to poor usability, especially for keyboard navigation and accessibility.

`FocusHandler` helps you save the current focus and restore it after the UI is closed.

```js
import FocusHandler from 'src/helper/focus-handler.helper';

const { PluginBaseClass } = window;

export default class OffCanvasLauncherPlugin extends PluginBaseClass {
    init() {
        this._focusHandler = new FocusHandler();

        this.el.addEventListener('click', () => {
            this._focusHandler.saveFocusState('myOffCanvas');
            this._openOffCanvas();
        });

        document.$emitter.subscribe('onCloseOffcanvas', () => {
            this._focusHandler.resumeFocusState('myOffCanvas', { preventScroll: true });
        });
    }

    _openOffCanvas() {
        // ...
    }
}
```

Typical use case:

- A customer clicks a button to open an offcanvas or modal.
- After closing it, the focus should return to that button.

Without proper focus handling, keyboard users can lose their position, navigation becomes confusing, and accessibility suffers. `FocusHandler` ensures a predictable and user-friendly focus flow.

---

You have now seen a curated selection of commonly used storefront helpers.

Of course, Shopware provides many more helpers. You can explore the full list [here](https://github.com/shopware/shopware/tree/trunk/src/Storefront/Resources/app/storefront/src/helper).

<Callout title="Legacy Helpers You Might Recognize (Deprecated in 6.8)" type="info">

In Shopware 6.7, you will still see these helpers in many projects:

- `DomAccess` (`src/helper/dom-access.helper.js`)
- `Iterator` (`src/helper/iterator.helper.js`)
- `ArrowNavigationHelper` (`src/helper/arrow-navigation.helper.js`)

They still exist in 6.7, but they are **deprecated for Shopware 6.8** and will be removed (ArrowNavigationHelper without replacement).

For new custom code, prefer native browser APIs:

- `DomAccess.*` → `el.querySelector(...)`, `el.querySelectorAll(...)`, `el.getAttribute(...)`, `el.dataset`, `el.hasAttribute(...)`.
- `Iterator.iterate(...)` → `array.forEach(...)`, `Object.keys(obj).forEach(...)`, `Array.from(...)`, `for (const [k, v] of formData.entries()) ...`.
- `ArrowNavigationHelper` → implement a small `keydown` handler in your component (or use an accessible listbox/combobox pattern).

</Callout>

## Storefront Utilities

In addition to helpers, Shopware storefront also provides `services` and `utilities`.

You can think of them like this:

- Helpers: Small logic utilities.
- Utilities: UI-related helpers (e.g., loading states, form handling)
- Services: Handle communication (e.g., API requests)

Below is a curated selection you will most likely run into in real projects.

### Loading Indicators

When triggering an async action (e.g., an API request), customers need feedback that something is happening.

A common pattern is:

- Disable the button.
- Show a loading indicator (loading spinner)
- Re-enable the button after completion

Shopware provides utilities for exactly this.

```js
import ButtonLoadingIndicatorUtil from 'src/utility/loading-indicator/button-loading-indicator.util';

const indicator = new ButtonLoadingIndicatorUtil(this.el, 'inner');
indicator.create();

// ... do async work ...

indicator.remove();
```

`ButtonLoadingIndicatorUtil` is especially useful for buttons and links. For a real `<button>`, `create()` disables the button and `remove()` enables it again. This prevents accidental double clicks while the request is running.

Without a loading indicator:

- Customers may click multiple times.
- Requests can be triggered multiple times.
- The UI feels unresponsive.

A loading indicator makes the interaction clear and prevents duplicate actions.

### Form Serialization

When working with forms, you often need to convert the form data into a usable format.

Common formats are:

- `FormData` (for `fetch` requests).
- JSON object (for custom processing or APIs).

Shopware provides a utility to handle this conversion for you.

```js
import FormSerializeUtil from 'src/utility/form/form-serialize.util';

const form = this.el.closest('form');

const formData = FormSerializeUtil.serialize(form);
const json = FormSerializeUtil.serializeJson(form);
```

#### Why Not Just `new FormData(form)`?

For simple `fetch()` form posts, `new FormData(form)` is also valid. In Shopware core, `FormSerializeUtil.serialize(form)` is mainly a small wrapper around `FormData` with an additional form check.

Use `FormSerializeUtil` when you want to follow Shopware's core pattern or when you also need `serializeJson()`. Use native `FormData` directly when your use case is simple and you do not need the extra helper behavior.

Typical use cases:

- Send form data via `fetch`.
- Transform form input into a JSON payload.
- Read and process user input in a structured way.

Manually collecting form values can be error-prone (e.g., missing fields, checkboxes, or arrays). `FormSerializeUtil` ensures that all inputs are correctly collected and converted into a consistent format.

### History Utility

Sometimes you want to update the URL (e.g., query parameters) without reloading the page.

This is useful for features such as filters, pagination, or search, where the state should be reflected in the URL. Shopware provides a utility for this.

```js
import HistoryUtil from 'src/utility/history/history.util';

// Example: update the URL query params without a full reload
HistoryUtil.pushParams({ 
    p: 2, 
    order: 'name-asc',
});
```

In this example:

- `p` is the page number (pagination).
- `order` is the sorting key.

You can pass more parameters. `HistoryUtil` does not validate them. It simply writes them into the URL. A parameter only has an effect if your code (or a Shopware core plugin, like the listing) reads and uses it.

Typical use cases:

- Update filter or sorting parameters.
- Reflect pagination state in the URL.
- Keep the URL in sync with the current UI state.

Updating the URL without reload allows customers to:

- Bookmark or share the current state.
- Navigate using the browser back/forward buttons.
- Keep context without losing the current page state.

### Timezone Utility

In storefront applications, the backend often needs to know the customer's timezone to display dates and times correctly.

Shopware provides a utility that detects the browser timezone and saves it in a cookie.

```js
import TimezoneUtil from 'src/utility/timezone/timezone.util';

// Sets a `timezone` cookie (if cookies are supported)
new TimezoneUtil();
```

Typical use cases:

- Display correct local time for orders or delivery dates.
- Render time-based content consistently between backend and frontend.

**Real-world example:**

Your backend renders a delivery date like “Arrives at 10:00”. Without the customer’s timezone, this time can be wrong for international customers.

By saving the timezone in a cookie, the backend can format dates and times consistently in server-rendered Twig responses and widgets.

Without timezone handling:

- Times may appear shifted or incorrect.
- Customers may see confusing or inconsistent timestamps.

### Modal Extension Utility

When working with dynamic content (e.g., loaded via AJAX or widgets), you may need to render that content inside a modal.

`PseudoModalUtil` helps you to create and update a modal without building a full modal logic yourself.

If your modal markup is static and already in Twig, you often do not need this utility.

```js
import PseudoModalUtil from 'src/utility/modal-extension/pseudo-modal.util';

// Pass the HTML content you want to show inside the modal.
const modal = new PseudoModalUtil('<div>Your dynamic content...</div>');
modal.open(() => {
    // After opening, you can (re-)initialize plugins inside the modal markup.
    window.PluginManager.initializePlugins();
});
```

Typical use cases:

- Load content via AJAX and display it in a modal.
- Replace modal content without reloading the page.
- Render dynamic UI inside a modal (e.g., forms).

If you inject HTML into the DOM, you often need to re-initialize storefront plugins. Use `window.PluginManager.initializePlugins()` to activate any storefront plugins inside the new content.

---

These above are common touchpoints in real projects. There are more utilities, feel free to explore it [here](https://github.com/shopware/shopware/tree/trunk/src/Storefront/Resources/app/storefront/src/utility)

## Storefront Services: Useful Clients

In addition to helpers and utilities, Shopware also provides `services`. Services are mainly used for communication with the backend (e.g., API requests or context updates).

Some storefront services are mainly relevant when you work with Shopware Apps:

- `AppClientService` (`src/service/app-client.service.ts`) fetches and caches app auth headers for requests.
- `ContextGatewayClient` (`src/service/context-gateway-client.service.ts`) updates the storefront context (e.g., language or currency) and can handle redirects.

### HTTP Requests in the Storefront (`fetch()`)

Shopware also includes a legacy `HttpClient` service based on `XMLHttpRequest`. For new code, use the native `fetch()` directly.

**Important:** Many storefront endpoints expect the header `X-Requested-With: XMLHttpRequest`.

This does not mean that you must use the old `XMLHttpRequest` API. The header is just a signal for Shopware's backend that the request is an AJAX/fragment request. Some controllers use this distinction to return the right response format or to handle storefront widgets correctly.

#### Example: Load a Widget/Fragment With `fetch()`

A common storefront pattern is:

- Load HTML from the backend (server-rendered Twig).
- Inject it into the DOM.
- Re-initialize storefront plugins.

**Example:**

Imagine you have a button “Show shipping info” that loads HTML from a widget endpoint and renders it into the page.

```js
import ButtonLoadingIndicatorUtil from 'src/utility/loading-indicator/button-loading-indicator.util';

const { PluginBaseClass } = window;

export default class ShippingInfoLoaderPlugin extends PluginBaseClass {
    static options = {
        url: null,
        targetSelector: '[data-shipping-info-target]',
    };

    init() {
        this._button = this.el;
        this._target = document.querySelector(this.options.targetSelector);

        this._button.addEventListener('click', () => this._load());
    }

    async _load() {
        if (this._isLoading || !this.options.url || !this._target) {
            return;
        }

        this._isLoading = true;
        const indicator = new ButtonLoadingIndicatorUtil(this._button, 'inner');
        indicator.create();

        try {
            const response = await fetch(this.options.url, {
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                },
            });

            if (!response.ok) {
                throw new Error(`Request failed: ${response.status} ${response.statusText}`);
            }

            this._target.innerHTML = await response.text();
            window.PluginManager.initializePlugins(); // In case the fragment contains new plugin markup.
        } finally {
            indicator.remove();
            this._isLoading = false;
        }
    }
}
```

This pattern is common in Shopware storefront development:

- Twig renders HTML.
- Storefront loads it via `fetch()`.
- The HTML is injected into the DOM.
- Storefront plugin instances must be re-initialized.

Understanding this flow helps you build dynamic features without full page reloads.

## Storefront Core Plugins (Most Common Touch Points)

Shopware ships many storefront plugins out of the box. You do not need to memorize all of them.

What matters is that you know they exist, what they roughly do, and where to find them.

These storefront plugins are common integration points in real projects.

- **`AddToCartPlugin`**: Submits “add to cart” forms and can trigger the off-canvas cart.
- **`OffCanvasCartPlugin`**: Opens and manages the off-canvas cart UI and its behavior.
- **`ListingPlugin`**: Controls filters, sorting, and pagination for product listings.
- **`SearchWidgetPlugin`**: Provides search suggestions while typing in the header search.
- **`FormAjaxSubmitPlugin`**: Submits a form via AJAX without a full page reload.

If you want to check all storefront plugins Shopware provides, feel free to check the [official documentation](https://developer.shopware.com/docs/resources/references/storefront-reference/plugin-reference.html) or check the [source code](https://github.com/shopware/shopware/tree/trunk/src/Storefront/Resources/app/storefront/src/plugin) directly.

### How to Dock In Safely

In Shopware storefront, “configuration” is not only `config.xml`. It usually means: change behavior via existing options instead of replacing code. Common sources are:

- **Plugin config (`config.xml`)** → Twig reads it and passes values into the HTML.
- **Data attributes** like `data-...-options` (and sometimes `data-...-config`) → your plugin reads them into `this.options`.

When you need to change existing behavior, prefer these strategies (in this order):

- **Configure first**: If a feature can be controlled via options (`config.xml`, `data-...-options`), do that.
- **Extend/override second**: If options are not enough, extend or override a core storefront plugin.
- **Copy code last**: Copying core code increases maintenance and upgrade risk.

#### Importing Core Code vs. Copying Core Code

The import path `src/...` is a storefront build alias. It lets your plugin import Shopware's storefront helpers, utilities, services, or plugin classes during the JavaScript build.

This is different from copying Shopware core code into your own plugin. Importing a stable helper or utility keeps you close to the core behavior. Copying whole core files or relying on private internals makes updates harder, because you then own that copied code and must compare it against Shopware changes after an update.

After a Shopware update, rebuild and test your storefront JavaScript so your plugin is compiled against the version you actually run.

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

When extending a core storefront plugin, avoid depending on private methods (prefixed with `_`). In JavaScript this is usually just a convention, not a strict access modifier.

If your change needs these internals, treat it as a high upgrade risk and document it in your project.

</Callout>

## Common Pitfalls and Debugging

When working with storefront plugins, certain issues appear again and again. Knowing these patterns helps you debug much faster.

### Typical Failure Scenarios

The following issues are very common when working with storefront plugins. In many cases, one of these patterns is the root cause:

- A helper/service import is wrong (bundle compiles, but feature is missing).
- A storefront plugin instance runs, but not on the expected element (wrong selector / missing data attribute).
- You inject new HTML (AJAX) but forget to re-initialize plugins.
- A request works in the browser, but fails in JS (missing `X-Requested-With` header).

### Quick Console Checks

When something does not behave as expected, you can inspect the storefront plugin system directly in the browser console.

In many cases, debugging storefront issues means understanding which plugin is active on which element and which plugin has an **instance** on which page.

```js
// What plugins are registered?
PluginManager.getPluginList();

// Does this element have a plugin instance?
const el = document.querySelector('[data-buy-box]');
PluginManager.getPluginInstanceFromElement(el, 'BuyBox');
```

#### Quick Console Reference: `PluginManager`

When you debug storefront JavaScript, these `PluginManager` methods are very useful:

- `PluginManager.getPluginList()`
  Shows all registered plugin names.
- `PluginManager.getPlugin('MyPluginName')`
  Shows the plugin definition (e.g., registrations, options, active, instance).
- `PluginManager.getPluginInstanceFromElement(el, 'MyPluginName')`
  Returns the plugin instance for a specific DOM element.
- `PluginManager.initializePlugins()`
  Scans the DOM again and initializes plugins (useful after injecting HTML dynamically).

<Callout title="Tip: Force a Breakpoint" type="neutral">

If you want to pause execution at a specific point, insert `debugger;` into your plugin code path and reload with DevTools open. Remove it after debugging.

</Callout>

## Summary

In this learning unit, you learned:

- How to think about core storefront features as plugins, helpers, and services/utilities.
- Which core building blocks are common and worth reusing (`Debouncer`, `ViewportDetection`, selected core plugins).
- A practical pattern for loading server-rendered HTML via `fetch()` and safely re-initializing plugins.

With this knowledge, you have a solid overview of Shopware's storefront building blocks, so you know what is available and can work more confidently with storefront JavaScript.
