tenbruggencate/multibrand-lite

Shopware 6 multi-brand layer: serve up to 5 brand domains from one shop, hostname-resolved brand tokens injected into the page head.

Maintainers

Package info

bitbucket.org/Bruggencate/sw-plugin-multibrand-lite

Homepage

Issues

Documentation

Type:shopware-platform-plugin

pkg:composer/tenbruggencate/multibrand-lite

Statistics

Installs: 1

Dependents: 0

Suggesters: 0


README

Ten Bruggencate Development

Multi-Brand Lite

Multi-Brand Lite

Hostname-driven brand layer for Shopware 6. One Shopware instance serves multiple brand domains; each request resolves to a brandKey in Twig and a set of --brand-* CSS custom properties in the page head.

License: MIT ยท Shopware: 6.7.x ยท PHP: 8.1 / 8.2

๐Ÿ‡ฌ๐Ÿ‡ง English ยท ๐Ÿ‡ณ๐Ÿ‡ฑ Nederlands ยท ๐Ÿ‡ฉ๐Ÿ‡ช Deutsch

Lite tier โ€” and what's coming in Pro

Multi-Brand Lite (this package, tenbruggencate/multibrand-lite) is the free tier of the Multi-Brand family. It handles the foundation: hostname-based brand resolution, per-brand theme variables (CSS custom properties), Twig helpers, and per-sales-channel config. If you just need "serve brand A from brand-a.tld and brand B from brand-b.tld with their own colours, fonts and logo", Lite is the whole answer.

Multi-Brand Pro (coming as tenbruggencate/multibrand-pro) will layer on the per-brand content + commerce surfaces that the Lite tier deliberately leaves out:

  • Per-brand CMS pages โ€” distinct landing pages, About, FAQ etc. per brand without forking the sales channel
  • Per-brand email templates โ€” order confirmations, shipping notifications, password resets all branded per host
  • Per-brand checkout & shipping rules โ€” different payment methods, shipping costs or thresholds per brand
  • Brand-specific analytics โ€” segment Matomo / Plausible dashboards per brand for clean per-brand reporting
  • Brand A/B testing โ€” run experiments scoped to one brand without polluting the others

Pro is on the roadmap, not yet released. Lite alone is production-ready for the "shared catalogue, separate visual identity" use case and stays free.

Screenshots

Storefront homepage on desktop with multibrand active โ€” header logo, navigation, hero, all rendered with the per-host brand tokens injected into the page head.

Storefront homepage โ€” desktop, multibrand resolved per host

Storefront homepage on mobile โ€” same brand tokens, fully responsive.

Storefront homepage โ€” mobile (responsive)

What it does

Serving two or three brand domains from a single Shopware install normally forces you to either (a) fork templates per brand or (b) duplicate the whole store as separate sales channels. Both paths are expensive to maintain. This plugin takes the middle path:

  • One host โ†’ one brandKey โ€” configure which hostnames resolve to which brand in the admin
  • Per-brand CSS tokens โ€” amethyst vs forest vs whatever you want, emitted as var(--brand-primary) etc. so the same SCSS compiles once and renders differently per host
  • Twig helper โ€” {{ brand() }} returns the current brand object; templates stay simple

No per-brand plugins, no duplicate theme compilation, no forked templates. Just a thin resolution layer and a token system.

Install

Multi-Brand Lite is free and distributable via Composer / Packagist or via the Shopware Store.

composer require tenbruggencate/multibrand-lite
bin/console plugin:refresh
bin/console plugin:install --activate TenBruggencateMultiBrand
bin/console cache:clear

The plugin class name stays TenBruggencateMultiBrand (grandfathered from v1.x โ€” only the composer package name changed in v2.0.0). If you're upgrading from v1.x see the v2.0.0 changelog entry for the one-line composer manifest update.

Configuration

Configurable per sales channel from Settings โ†’ System โ†’ Plugins โ†’ Multi-Brand Layer.

FieldPurpose
brandsJSON-editable list of { key, label, hosts[], tokens{} } entries
defaultBrandFallback brandKey when no host matches (e.g. on admin preview URLs)
tokenPrefixCSS custom-property prefix (default --brand-)

Tokens in each brand entry are free-form โ€” ship whatever your theme reads. Common examples: primary, secondary, surface, text-strong, accent-1, accent-2.

Usage

In any Twig template:

{% set brand = brand() %}
<body data-brand="{{ brand.key }}">
  <h1>Welcome to {{ brand.label }}</h1>
</body>

meta.html.twig automatically emits the <style> block with --brand-* tokens; your SCSS reads them with graceful fallbacks:

.button--primary {
  background: var(--brand-primary, #7C5BA5); // amethyst fallback
}

Standards

  • Performance โ€” resolution happens once per request, cached per host. Token block adds ~400 bytes to <head>; no runtime JS.
  • SEO โ€” no impact on URL structure, canonicals, or sitemaps. Each brand's sales channel retains its own SEO settings.
  • WCAG โ€” CSS custom properties are a progressive enhancement; colour-contrast guarantees must be satisfied per-brand by the theme consuming the tokens (this plugin doesn't enforce them). Axe audit on a storefront page with the resolver active (0 violations on the plugin's own injection; theme-owned violations out of scope): docs/ACCESSIBILITY.md.
  • GDPR โ€” stateless. No cookies, no tracking, no data stored per visitor. Full data-flow + subject-rights documentation in GDPR.md.
  • Security โ€” host resolution uses Request::getHost() (trusted-proxy aware); no user-controllable input touches the brand lookup.
  • Uninstall โ€” plugin:uninstall --keep-user-data preserves all brand config; plugin:uninstall without the flag drops every TenBruggencateMultiBrand.config.* row so the destructive path leaves no trace. No owned tables.

Production-readiness checklist

A deliberately short list of things to verify before enabling the plugin on a customer-facing storefront. Not legal cover โ€” the MIT license already disclaims warranty โ€” but practical guidance an experienced operator would want anyway.

  • [ ] Configure trusted_proxies if you sit behind a CDN / load balancer. Request::getHost() reads the Host header (or X-Forwarded-Host when proxies are trusted). Without trusted proxies set, the wrong hostname can resolve and visitors see the default brand instead of yours.
  • [ ] Set defaultBrand to a valid existing brand key. This is the fallback for admin preview URLs and unmatched hostnames; pointing it at a non-existent key means the storefront renders with empty tokens and a broken data-brand attribute.
  • [ ] Test each brand domain on staging. Hit curl -I https://brand-a.staging.tld/ and grep the response for data-brand="brand-a" and the brand-specific --brand-primary token; if either is missing, host resolution isn't matching.
  • [ ] Don't try to use hreflang between brand hostnames โ€” see the GEO note below for why. If your "brands" are actually locale-variants, use Shopware's SalesChannelDomain per-language config instead and uninstall this plugin.
  • [ ] Theme contrast is per-brand, not per-plugin. This plugin injects tokens; your theme has to satisfy WCAG colour-contrast for every brand combination. Run an axe-core audit on each brand domain before launch.

Compatibility

Core platform

ShopwarePHPStatus
6.7.x โ€” tested against 6.7.8, 6.7.98.1, 8.2Stable
6.6.xโ€”Not supported
6.5.x and earlierโ€”Not supported

Database

EngineVersionNotes
MySQL8.0+Primary target; JSON functions used for config-row manipulation in migrations
MariaDB10.11+Tested end-to-end; earlier versions lack some JSON operator support

Browsers (storefront)

Evergreen browsers only โ€” the two most recent stable releases of each:

BrowserDesktopMobile
Chrome / Chromiumโœ…โœ…
Firefoxโœ…โœ…
Safariโœ… (macOS)โœ… (iOS 16+)
Edgeโœ…โ€”

Internet Explorer and legacy Edge are not supported. The plugin emits no runtime JS, so graceful degradation on older browsers usually still renders content, just without progressive enhancements.

Admin browsers

Same evergreen matrix โ€” the Shopware admin is Vue-based and has its own compatibility baseline that this plugin doesn't extend or narrow.

Development

ToolVersionScope
PHPโ‰ฅ 8.1Runtime + test suite
Composer2.xDependency management
Node.jsโ‰ฅ 18Only needed if you edit SCSS and re-run the theme compile

Accessibility

WCAG 2.2 level A + AA โ€” see docs/ACCESSIBILITY.md for axe-core audit output and per-page violations.

What we test before each release

  • Full PHPUnit unit suite against PHP 8.1 + 8.2 (source-inspection tests don't need a kernel)
  • PHPStan level 8 + PHP-CS-Fixer (@PSR12 + @Symfony)
  • Composer validate
  • Live-DB smoke tests (plugin install โ†’ activate โ†’ brand resolves per host โ†’ uninstall cycle) against Shopware 6.7.8

Uninstall note

If you also install a theme plugin or another plugin that declares a composer dependency on tenbruggencate/multibrand-lite (or, for legacy installs, the pre-v2.0.0 name tenbruggencate/multibrand), Shopware's plugin manager will refuse to uninstall MultiBrand while that dependant is still active โ€” you'll see PluginHasActiveDependantsException. This is correct Shopware behaviour, not a MultiBrand bug: uninstalling MultiBrand first would break the dependant plugin at runtime.

To uninstall MultiBrand cleanly:

  1. Uninstall the dependant plugin(s) first, or remove the require entry from their composer.json and re-run composer update.
  2. Then bin/console plugin:uninstall TenBruggencateMultiBrand.

For vanilla MultiBrand installs (no composer-dependant plugin on top), uninstall runs unblocked: the plugin drops all TenBruggencateMultiBrand.config.* rows from system_config and removes itself.

GEO / multi-region note โ€” why there is no hreflang

This plugin deliberately does NOT emit hreflang alternate links between the configured brand hostnames. That is correct, not an oversight:

  • hreflang is the signal for "the same canonical content is served in multiple languages or regions" โ€” e.g. example.com/product (en) vs. example.de/product (de) for the same product.
  • The domains MultiBrand is designed for are separate brands with their own catalogues, copy, and identity, not locale-variants of the same storefront. They just share a single Shopware instance for operational reasons.
  • Cross-linking them via hreflang would tell Google "these are equivalent pages, pick one" โ€” which is the opposite of what multi-brand storefronts want.

If your brands really ARE locale-variants (e.g. you have shop.nl / shop.de / shop.fr serving the same products in different languages), Shopware's native SalesChannelDomain per-language config is the right tool โ€” leave this plugin out of that use case.

Related plugins

Free Packagist-distributed siblings from the same publisher:

Support

License

MIT ยฉ Ten Bruggencate Development

More from Ten Bruggencate Development

Focused, privacy-first Shopware 6 plugins โ€” free to start, upgrade when you grow:

  • Newsletter โ€” Lite: GDPR-safe signup & subscriber-list management ยท Pro: campaigns, lifecycle automations, native-data segmentation
  • Legal Pages โ€” Lite: localized legal-page templates ยท Pro: the compliance toolkit (maintained templates, cookie policy, accessibility statement)
  • Analytics โ€” multi-backend (Matomo / Plausible), GDPR-first, cookieless-capable
  • Maintenance โ€” a branded, SEO-correct maintenance page
  • Multi-Brand โ€” Lite: hostname-based brand resolution + per-brand theme tokens ยท Pro (coming): per-brand CMS pages, email templates, checkout & shipping rules, analytics and A/B testing
  • Product Encyclopedia โ€” structured educational content pages linked to products
  • Seasons โ€” scheduled theme-config variants