sulu/product-bundle
Sulu Product Bundle adds product management to the Sulu CMS, letting you create and organize product entities, manage attributes and pricing, and integrate catalog data into your Sulu-based websites and applications.
This is a Sulu 1.x era Symfony bundle (last release May 2018), so treat it as legacy integration work rather than greenfield. It requires Symfony 3.x/4.x, PHP 7.x, Doctrine ORM, and a matching sulu/sulu core (^1.6).
Minimal steps:
composer require sulu/product-bundle and pin the version that matches your Sulu core.app/AppKernel.php (or config/bundles.php) after SuluCoreBundle, SuluAdminBundle, and SuluContentBundle — it depends on their services and admin navigation.sulu_product_api:
type: rest
resource: "@SuluProductBundle/Resources/config/routing_api.xml"
prefix: /admin/api
Resources/config/doctrine/. Either rely on auto-registration or add Sulu\Bundle\ProductBundle\Entity to your doctrine.orm.mappings.bin/console doctrine:schema:update --force in dev, or generate a migration for prod.bin/console sulu:build (or assets:install + assetic:dump in older setups) so the product tab appears.Where to look first:
Entity/Product.php, Entity/ProductTranslation.php — the data model.Product/ProductManager.php + ProductManagerInterface — the public service surface.Product/ProductRepository.php — how filtering/pagination actually works.Controller/ProductController.php — the REST contract you'll mirror.Resources/config/services.xml — service IDs (sulu_product.product_manager, sulu_product.product_repository).First use case: inject sulu_product.product_manager into your own service and call save() / findById() to create or read products; alternatively hit GET /admin/api/products?flat=true&limit=10 to confirm the admin API is wired up before writing any PHP.
Service-layer reads and writes. Always go through ProductManagerInterface and ProductRepositoryInterface rather than the EntityManager. The manager handles translations, URL generation, and event dispatching that the raw entity will miss.
public function __construct(
private ProductManagerInterface $productManager,
private ProductRepositoryInterface $productRepository
) {}
$product = $this->productManager->save([
'name' => 'Widget',
'locale' => 'en',
'status' => Product::STATUS_ACTIVE,
], $existingId);
Filtered listing. ProductRepositoryInterface::findByFilters($filters, $sortBy, $sortOrder, $limit, $offset, $locale) mirrors the admin list behaviour. Pass the same array shape the REST API sends so your custom controller and the admin UI stay in sync.
Custom attributes. Extend ProductAttribute by creating a ProductAttributeValue per product, keyed by the attribute entity. Keep the attribute-type string in sync with what the admin form renders — a typo silently yields a product with no value rather than an exception.
Admin UI extension. Override the product form templates by placing files at app/Resources/SuluProductBundle/views/... (or the bundle's override path in your project). Use the block/sulu_form structure already present rather than inventing new fields, so the REST serializer and the form stay aligned.
Content integration. For editors picking products in pages, use the product_selection content type; for generated product listings (e.g. a shop category page), register a smart-content data provider or reuse the shipped one. This is the highest-value integration for a CMS-driven shop and avoids hand-rolling Twig queries.
Events for cross-cutting concerns. Subscribe to ProductEvents::PRE_PERSIST, POST_PERSIST, PRE_DELETE for cache invalidation, search indexing, or ERP sync. These fire from the manager, so direct ORM writes bypass them — another reason to always use the manager.
Headless usage. The REST controller is a complete CRUD surface (GET/POST/PUT/DELETE /admin/api/products). If you're building a storefront separately, point your consumer at this API rather than importing the entities, and let the bundle own the schema.
$product->setLocale($locale) before saving and make sure the fallback locale is configured in sulu_core; otherwise getTranslation() returns null and admin forms render blank.type on ProductAttribute fails at render time, not at persist time. Validate on your side and keep a constant map in your own code.findByFilters expects Sulu's RestHelper keys (flat, ids, category), not arbitrary Doctrine criteria. If results are unexpectedly empty, log the composed query with doctrine.dbal.logging enabled.bin/console sulu:build (or the Assetic pipeline) — otherwise changes are invisible in the admin and you'll chase a phantom bug.bin/console sulu:content:reindex (or your project's equivalent) or listings go stale.ProductNotFoundException (bad ID), ProductDependencyException (delete blocked by relations), and Doctrine MappingException (bundle mappings not registered). Enable sulu_product channel logging at debug to trace manager calls.How can I help you explore Laravel packages today?