Search by

tenbruggencate / multibrand-lite

guy@tenbruggencatedevelopment.nl

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

Package info

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

Homepage

Issues

Documentation

Type:shopware-platform-plugin

pkg:composer/tenbruggencate/multibrand-lite

Statistics

Installs: 130

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 Pro adds

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 a five-slot brand config screen. 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 (tenbruggencate/multibrand-pro) is released and installs side by side with Lite โ€” it requires Lite and extends it rather than replacing it. Pro is not published on Packagist: it is delivered directly today, and a Shopware Store listing is in preparation. What it adds:

  • Per brand its own subject, sender name, HTML body and plain-text body for every transactional email type
  • Applied automatically at send time on every path (Flow Builder or direct); the brand is derived from the sales channel's domain
  • Override only what you want: empty fields fall back to the Shopware default; a toggle to pre-stage overrides before arming them
  • Fail-safe: on any error the email goes out unchanged and the error is logged; Shopware's mail templates are never touched
  • Managed under Settings โ†’ Multi-Brand Pro with list and form, including hints for brand keys and template types; EN/NL/DE
  • Export all data button: JSON with overrides and configuration, also via API; keep or remove data on uninstall

โ†’ What's in Multi-Brand Pro

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

Configured from Settings โ†’ System โ†’ Plugins โ†’ Multi-Brand Lite โ†’ Config. Brand definitions are read from the global config namespace and are deliberately not overridable per sales channel โ€” a brand is hostname-derived, so two sales channels on the same host share one brand by definition (see the design-decision note in BrandResolver). Shopware still renders its sales-channel selector on this screen; values saved against a specific sales channel are ignored at render time. The screen has a getting-started card, one card per brand slot โ€” Brand 1 (default) through Brand 5 โ€” and a JSON backup/restore card. Each slot N (1โ€“5) holds:

FieldPurpose
brandNKeyMachine key for the brand, e.g. north โ€” returned by brand_key() and rendered as data-brand
brandNHostnamePatternHostname fragment that resolves to this brand (substring match, first match wins)
brandNName, brandNEmailDisplay name and contact address for templates and the Organization / WebSite JSON-LD
brandNLogoPath, brandNFaviconBasePathAsset paths for the brand logo and its favicon set
brandNLegalNameRegistered company name, emitted as schema.org legalName when it differs from the display name
brandNStreetAddress, brandNPostalCode, brandNCity, brandNCountryThe PostalAddress block. brandNCountry must be an ISO 3166-1 alpha-2 code (two letters, normalised to upper case); anything else is dropped with a log warning
brandNTelephone, brandNVatIdschema.org telephone and vatID
brandNSameAsTextarea, one absolute http(s) URL per line โ€” the brand's own social profiles and its Wikidata / Wikipedia entry. Lines that are not http(s) URLs are skipped, the rest are kept
brandNPrimary, brandNSecondary, brandNSurface, brandNAccent1โ€“3, brandNTextStrong, brandNTextSoftThe eight colour tokens, emitted as --brand-* CSS custom properties

The eight organisation fields (brandNLegalName through brandNSameAs) are optional and feed the per-brand Organization JSON-LD in meta.html.twig. Every one of them is omitted from the emitted data when empty, so a shop that never fills them in gets exactly the structured data it got before v2.2.0 โ€” plus the logo, which since v2.2.0 is emitted as an ImageObject. Logo fallback rule: the brand's own brandNLogoPath wins whenever it is set (made absolute against the request's scheme + host, or passed through untouched when it is already an absolute URL); the shared theme logo (theme_config('sw-logo-desktop')) is only used when the resolved brand has no logo path. On a multi-brand shop the theme logo is the same image for every brand, which is exactly the problem this rule solves.

Colour values must be 6-digit hex; anything else is replaced by a neutral default instead of being emitted into the page. Brand 1 is the fallback โ€” when no hostname pattern matches (admin preview URLs, direct IP access), brand 1 renders. Overlapping patterns between slots are logged.

Usage

In any Twig template:

<body data-brand="{{ brand_key() }}">
  <h1>Welcome to {{ brand_config('name') }}</h1>
  {% if is_brand('north') %}<p>Only on the north brand.</p>{% endif %}
</body>

brand_config() reads one field of the resolved brand: key, name, email, primary, secondary, surface, accent1โ€“accent3, textStrong, textSoft, logoPath, faviconBasePath, legalName, streetAddress, postalCode, city, country, telephone, vatId and sameAs (a list of validated URLs, every other field a string). brand_css_properties() returns the colour tokens as a --brand-* map; the routing config (hostnamePattern) is deliberately not exposed to templates.

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, behavioural segmentation, product-pull blocks
  • Legal Pages โ€” Lite: localized legal-page templates ยท Pro: compliance toolkit (cookie consent, accessibility statement, imprint validator, audit trail)
  • Analytics โ€” Lite: multi-backend (Matomo / Plausible), GDPR-first ยท Pro: e-commerce conversion tracking
  • Maintenance โ€” Lite: branded, SEO-correct (HTTP 503) maintenance page ยท Pro: scheduled maintenance windows
  • Multi-Brand โ€” Lite: serve multiple brands from one Shopware, by hostname ยท Pro: per-brand transactional email overrides
  • Product Encyclopedia โ€” Lite: structured educational content pages ยท Pro: per-profile related-product cross-links
  • Seasons โ€” Lite: scheduled theme-config variants ยท Pro: per-variant marketing banners

Need more than a plugin? ten Bruggencate Development builds and customizes Shopware 6 stores. โ†’ Let's talk