Skip to content

Storefront JS

Guppy follows the standard Shopware storefront JS pattern: PluginBase classes with a lifecycle, registration with PluginManager, selector-based activation via data-* attributes. This page covers architecture, bundled plugins, configuration, and how to write your own.

Plugin architecture

Registration

javascript
// New plugins
PluginManager.register('SplideSliderPlugin', SplideSliderPlugin);
PluginManager.register('UspBannerPlugin', UspBannerPlugin);
PluginManager.register('ProductBoxClickPlugin', ProductBoxClickPlugin);

// Plugin overrides (lazy imports possible)
PluginManager.override('QuantitySelector', () => import('./js/quantity-selector.plugin'));

Initialisation via data-* attributes

html
<div data-splide-slider-plugin="true"
     data-splide-slider-plugin-options='{...}'>

Bundled plugins

SplideSliderPlugin

Modern slider based on Splide.js: replaces Shopware's default Tiny Slider.

Features: better performance, full keyboard navigation, screen-reader support, touch gestures, responsive.

javascript
export default class SplideSliderPlugin extends Plugin {
    static options = {
        splideSnippets: ""
    }

    init() {
        this._mountSlider(this.el, this.options.splideSnippets);
    }

    _mountSlider(element, options) {
        var splideSlider = new Splide(element, {
            i18n: options
        }).mount();

        const splideOptions = Object.getPrototypeOf(splideSlider.options);
        if (splideOptions.autoplay === "pause") {
            splideSlider.Components.Autoplay.play();
        }
    }
}

Usage:

html
<div class="splide" data-splide-slider-plugin="true">
    <div class="splide__track">
        <ul class="splide__list">
            <li class="splide__slide">Slide 1</li>
            <li class="splide__slide">Slide 2</li>
        </ul>
    </div>
</div>

UspBannerPlugin

Interactive USP banner with tooltip support and responsive behaviour.

Features: tooltip system, responsive display, slider integration on mobile, configurable layouts (default and benefits).

json
{
    "guppy-usp-active": true,
    "guppy-usp-layout": "benefits",
    "guppy-usp-layout-benefit1": "Free shipping",
    "guppy-usp-layout-benefit2": "30-day returns",
    "guppy-usp-layout-benefit3": "Fast delivery"
}

Responsive behaviour:

ViewportUSP count
Desktop1–4 static
Tablet1–2 as a slider with infinite loop
Mobile1 as a slider with infinite loop

ProductBoxClickPlugin

Extends the product box with fully clickable cards plus keyboard support.

Features: full-box click target, keyboard support, correct focus management, event delegation.

CustomCheckoutPlugin

Improves the checkout experience (validation, progress tracking, auto-save, error handling).

html
<div class="checkout-main" data-custom-checkout-plugin="true">

QuantitySelectorPlugin (override)

Extends Shopware's quantity selector: input validation, arrow-key navigation, ARIA labels, smooth animations.

Other plugins

PluginPurpose
RemoveExtraH1PluginSEO, removes redundant H1 tags after DOM load.
DeliveryInformationMarginPluginDynamic margin adjustment for delivery information.
SplideSliderGalleryPluginExtended gallery with thumbnail sync and zoom.
CollapseFooterColumnsPlugin (override)Accordion footer on mobile.

Splide configuration

All Splide.js options are available.

html
<div class="splide"
     data-splide-slider-plugin="true"
     data-splide-slider-plugin-options='{
       "type": "loop",
       "perPage": 4,
       "perMove": 1,
       "gap": "1rem",
       "pagination": false,
       "arrows": true,
       "autoplay": true,
       "interval": 5000,
       "pauseOnHover": true,
       "breakpoints": {
         "1024": { "perPage": 3 },
         "768": { "perPage": 2 },
         "576": { "perPage": 1 }
       }
     }'>
OptionTypeDefaultDescription
typestringslideslide, loop, fade
perPagenumber1Visible slides
perMovenumber1Slides per move
gapstring0Gap between slides
arrowsbooleantrueNavigation arrows
paginationbooleantruePagination dots
autoplaybooleanfalseAuto play
intervalnumber5000Autoplay interval (ms)
pauseOnHoverbooleantruePause on hover
rewindbooleanfalseRewind to start
speednumber400Animation speed (ms)
easingstringcubic-bezier(0.25, 1, 0.5, 1)Easing function

Events

javascript
document.addEventListener('DOMContentLoaded', () => {
    const splideEl = document.querySelector('.splide');
    const splide = splideEl._splide;

    if (splide) {
        splide.on('move', (newIndex, prevIndex) => {
            console.log(`Moved from ${prevIndex} to ${newIndex}`);
        });

        splide.on('mounted', () => console.log('Slider mounted'));
        splide.on('autoplay:play', () => console.log('Autoplay started'));
        splide.on('autoplay:pause', () => console.log('Autoplay paused'));
    }
});

ProductBoxClickPlugin

javascript
export default class ProductBoxClickPlugin extends Plugin {
    static options = {
        excludeSelectors: '.btn, .product-action, .wishlist-button, input, select',
        linkSelector: '.product-name a, .product-image-link'
    }

    init() {
        this.el.addEventListener('click', this._onClick.bind(this));
        this.el.addEventListener('keydown', this._onKeydown.bind(this));
    }

    _onClick(event) {
        if (event.target.closest(this.options.excludeSelectors)) {
            return;
        }

        const link = this.el.querySelector(this.options.linkSelector);
        if (link) {
            link.click();
        }
    }

    _onKeydown(event) {
        if (event.key === 'Enter' || event.key === ' ') {
            this._onClick(event);
        }
    }
}

Write your own plugin

Plugin template

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

export default class MyCustomPlugin extends Plugin {
    static options = {
        myOption: 'default-value',
        anotherOption: true
    };

    init() {
        this._registerEvents();
    }

    _registerEvents() {
        this.el.addEventListener('click', this._onClick.bind(this));
    }

    _onClick(event) {
        console.log('Clicked!', this.options.myOption);
    }

    destroy() {
        // Cleanup
    }
}

Register the plugin

javascript
// main.js
import MyCustomPlugin from './plugins/my-custom-plugin';

window.PluginManager.register('MyCustomPlugin', MyCustomPlugin, '[data-my-custom-plugin]');

Use the plugin

html
<div data-my-custom-plugin="true"
     data-my-custom-plugin-options='{"myOption": "custom-value"}'>
    <!-- content -->
</div>

Debugging

javascript
// Get a plugin instance
const element = document.querySelector('[data-splide-slider-plugin]');
const pluginInstance = window.PluginManager.getPluginInstanceFromElement(element, 'SplideSliderPlugin');

// Inspect options
console.log(pluginInstance.options);

// All registered plugins
console.log(window.PluginManager.getPluginList());

Common issues

ProblemCauseFix
Plugin doesn't loadJS errorCheck the browser console
Slider shows no slideswrong HTML structureVerify Splide classes
Events don't fireplugin not initialisedwait for DOMContentLoaded
Autoplay doesn't workautoplay: "pause" setchange to true
  • Architecture: plugin structure and build output (dist/storefront/js/).
  • Twig Overrides: set data-* attributes correctly from templates.
  • DmfSplideSlider: shared slider component consumed by many custom elements.