madcoders / sylius-brand-plugin
Brand plugin for Sylius 2.x - manage brands in the admin, resolve them from a product attribute, and show a brand overview, brand product listings and brand badges in the shop.
Package info
github.com/mad-coders/sylius-brand-plugin
Type:sylius-plugin
pkg:composer/madcoders/sylius-brand-plugin
Requires
- php: ^8.3
- monsieurbiz/sylius-settings-plugin: ^2.0
- sylius/sylius: ^2.0
- symfony/lock: ^6.4 || ^7.4
Requires (Dev)
- behat/behat: ^3.16
- dbrekelmans/bdi: ^1.4
- dmore/behat-chrome-extension: ^1.4
- dmore/chrome-mink-driver: ^2.9
- friends-of-behat/mink: ^1.11
- friends-of-behat/mink-browserkit-driver: ^1.6
- friends-of-behat/mink-debug-extension: ^2.1
- friends-of-behat/mink-extension: ^2.7
- friends-of-behat/page-object-extension: ^0.4
- friends-of-behat/suite-settings-extension: ^1.1
- friends-of-behat/symfony-extension: ^2.6
- friends-of-behat/variadic-extension: ^1.6
- nyholm/psr7: ^1.8
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- rector/rector: ^2.0
- sylius-labs/coding-standard: ^4.4
- sylius-labs/suite-tags-extension: ~0.2
- sylius/sylius-rector: ^3.8
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4
- symfony/debug-bundle: ^6.4 || ^7.4
- symfony/dotenv: ^6.4 || ^7.4
- symfony/http-client: ^6.4 || ^7.4
- symfony/intl: ^6.4 || ^7.4
- symfony/runtime: ^6.4 || ^7.4
- symfony/web-profiler-bundle: ^6.4 || ^7.4
- symfony/webpack-encore-bundle: ^2.2
This package is auto-updated.
Last update: 2026-08-28 09:50:45 UTC
README
Brands for Sylius 2.x: a brand overview page, per-brand product listings, and brand badges on the product page and product tiles - driven by a product attribute you already have.
Status: 1.0 release candidate. Functionally complete and green across the supported Sylius/Symfony/database matrix. The API - service ids, settings paths, table names - is frozen for 1.0; what it still wants is real-world use. Please report anything you hit.
composer require madcoders/sylius-brand-plugin:^1.0@RCThe brand hookable is reusable on your own product grids - see Putting brands on your own product grids.
What it does
- Brands as a first-class resource. Code, logo, translatable name, slug and description, an enabled flag and four display toggles, all managed from a Sylius admin grid.
- Products map to brands through a product attribute. Most catalogues already carry the brand as an attribute (from a PIM, an import or a feed). Point the plugin at that attribute and it resolves each product to a brand - no re-tagging, no second source of truth.
- A mapping table for messy data.
"Nike","Nike Inc."and"NIKE Sportswear"can all map onto the singlenikebrand. Without a mapping the plugin assumes a 1:1 match between the attribute value and the brand code. - Shop pages. A brand overview at
/brands, a per-brand product listing at/brands/{slug}, a brand strip on the homepage, a brand badge on the product page and on product tiles - each one switched on per brand. - Reusable on your own grids. The brand hookable is configurable, so you can attach it to any hook that has a product in context without copying a template - see below. Twig helpers are provided for custom markup.
- One feature toggle. The whole feature can be turned off from the admin without touching configuration files or removing the bundle.
Configuration lives in the admin, through MonsieurBiz' Settings plugin.
Requirements
| PHP | ^8.3 |
| Sylius | ^2.0 (tested against ~2.0, ~2.1, ~2.2) |
| Symfony | ^6.4 || ^7.4 |
| Settings | monsieurbiz/sylius-settings-plugin ^2.0 |
| Database | MySQL 8.4 or MariaDB 11.4. PostgreSQL is not supported - see below. |
PostgreSQL. This plugin's own migration is platform-neutral, but
monsieurbiz/sylius-settings-plugin- a hard dependency - creates its table with raw MySQL DDL and no platform guard, so migrations fail on PostgreSQL before this plugin is reached. Nothing here can work around that; it has to be fixed upstream.
Installation
composer require madcoders/sylius-brand-plugin
Then follow docs/INSTALLATION.md - the plugin needs its bundle registered,
its configuration and routes imported, its entities declared, and the brand extension applied to
your Product.
How a product finds its brand
product attribute value mapping (optional) brand
"Nike Inc." ───────► nike ───────► Brand(code: "nike")
"NIKE Sportswear" ───────► nike ───────►
"adidas" ───────► (no entry: 1:1) ───────► Brand(code: "adidas")
The attribute stays the source of truth, but the resolved brand is denormalised onto
sylius_product.brand_id so listings, filters and sorting are ordinary indexed queries. The
column is kept in sync whenever a product is saved through the resource layer, and
bin/console madcoders:brand:resync-products rebuilds it in bulk after an import or a change to
the mapping. The reasoning is in
docs/adr-log/0004-brand-resolution-from-a-product-attribute.md.
Configuration
Admin → Settings → Brands:
| Setting | Scope | Meaning |
|---|---|---|
enabled |
per channel | Display toggle. When off, the shop routes 404 and every brand hook renders nothing. Products still resolve to their brand, so the data stays correct underneath. |
brand_attribute |
global | Code of the product attribute carrying the brand. |
brand_mapping |
global | Attribute value → brand code pairs. Empty means a 1:1 match. |
The last two are global on purpose: a product has one brand_id, so a per-channel attribute would
have no single right answer. They are only offered on the "all channels" tab, and a channel-scoped
value left behind by an older install is ignored.
Putting brands on your own product grids
The plugin ships the brand on three surfaces out of the box - the homepage strip, the product page and Sylius' product card. Anywhere else - a custom listing, search results, a cross-sell carousel, your own theme's card - you attach it yourself. There is nothing to copy: the same template the plugin uses is configurable, so you point a hook at it and pass configuration.
# config/packages/madcoders_sylius_brand.yaml sylius_twig_hooks: hooks: # any hook that has a product in its context 'sylius_shop.product.index.content.body.main.products': madcoders_brand: template: '@MadcodersSyliusBrandPlugin/shop/product/brand.html.twig' configuration: surface: product_tile priority: 50
| configuration | default | what it does |
|---|---|---|
context_key |
product |
where the product lives in the hook's context, if it isn't called product |
surface |
product_tile |
which display toggle to respect: homepage, product_page, product_tile, brand_overview, or any to ignore the toggles |
show_logo |
false |
render the logo before the name |
link |
true |
link to the brand page (falls back to plain text when that brand's page is not reachable) |
label |
false |
prefix with a translated "Brand:" label |
class |
muted small line | CSS classes for the wrapper |
link_class |
fw-semibold text-reset |
CSS classes for the name itself |
The template renders nothing when the feature is off, the product has no brand, the brand is disabled, or that brand's toggle for the chosen surface is off - so you never have to guard the hook yourself.
Twig helpers
If you want your own markup, use the helpers directly. These are public API.
| function | returns |
|---|---|
madcoders_brand_for(product, surface = 'product_tile') |
the brand to show for that product on that surface, or null |
madcoders_brands_enabled() |
whether the feature is on for the current channel |
madcoders_homepage_brands(limit = 12) |
brands flagged for the homepage, ordered by position |
{% set brand = madcoders_brand_for(product, 'any') %}
{% if brand is not null %}
<a href="{{ path('madcoders_sylius_brand_shop_brand_show', { slug: brand.slug }) }}">{{ brand.name }}</a>
{% endif %}
Use madcoders_brand_for() rather than reading product.brand. It applies the feature toggle,
the brand's enabled flag and the surface toggle for you - and, less obviously, it answers every row
of a listing from a single query. Reading product.brand directly initialises a Doctrine proxy per
row and loads the brand and its translation, which on a 12-product grid is up to 24 extra queries.
Admin helpers
| function | returns |
|---|---|
madcoders_brand_product_count(brand) |
how many products point at that brand |
madcoders_brand_diagnostics(product) |
the resolution chain: configured attribute, the product's value for it, what it maps to, and the resulting brand |
Development
make setup # deps + docker (MySQL on 3307) + assets + database make install-hooks # pre-commit quality gate and commit template make verify # fast gate: composer validate + phpstan + ecs + unit tests make test # phpunit + behat make help # every available target
Contributor guide: docs/CONTRIBUTING.md. Working on this with an AI agent?
Start from AGENTS.md.
The primary branch is 1.0 - this repository has no main or master, following the Sylius
version-branch model.
Credits
Built and maintained by Madcoders.