Sylius Plugin Development
Overview
Sylius is an open-source e-commerce framework built on top of Symfony and Doctrine ORM. Unlike monolithic e-commerce platforms where plugins hook into predefined extension points, Sylius leverages the full power of the Symfony ecosystem -- dependency injection, service decoration, event dispatching, and Twig templating -- to provide a highly customizable commerce engine. Sylius follows a resource-oriented architecture where every domain concept (product, order, customer, payment) is modeled as a "resource" managed by the ResourceBundle, giving plugins a consistent CRUD interface, API exposure, and admin panel integration out of the box.
The plugin system is built around standard Symfony bundles. A Sylius plugin is a Composer package containing a bundle class, service definitions, entity mappings, templates, and optional migrations. The official plugin skeleton (sylius/plugin-skeleton) provides a development environment with a fully functional Sylius application for testing. Plugins can extend or replace any part of the system -- add new entities, decorate existing services, override templates, inject form fields, customize the admin grid, and expose REST or GraphQL endpoints via API Platform.
Sylius 1.x targets Symfony 5.4/6.x, while Sylius 2.0 (in development) moves to Symfony 7.x with API Platform 3.x as the primary storefront interface. Modern plugin development should target both Sylius 1.13+ and prepare for Sylius 2.0 compatibility by avoiding deprecated patterns.
Plugin Structure
Every Sylius plugin is a Symfony bundle distributed as a Composer package. The bundle class registers the plugin with the Symfony kernel, and the composer.json declares the package type as sylius-plugin for automatic discovery.
Minimal Directory Layout
my-sylius-plugin/
src/
MyPlugin.php # Bundle class (extends AbstractBundle or Bundle)
DependencyInjection/
Configuration.php # Bundle configuration tree
MyPluginExtension.php # Loads services.xml/yaml
Entity/ # Doctrine entities
Repository/ # Custom repository classes
Form/
Extension/ # Form type extensions
Type/ # Custom form types
Resources/
config/
services.xml # Service definitions
doctrine/ # Doctrine mapping files (XML)
grids/ # Sylius Grid configuration
routing.yml # Route definitions
templates/ # Twig templates
translations/ # Translation files
tests/
Behat/ # Behat feature tests
PHPUnit/ # Unit/integration tests
composer.json
Bundle Class
The bundle class extends Symfony\Component\HttpKernel\Bundle\AbstractBundle (Symfony 6.1+) or Symfony\Component\HttpKernel\Bundle\Bundle. For plugins that register Doctrine entity mappings, extend Sylius\Bundle\ResourceBundle\AbstractResourceBundle and override getModelNamespace() and getModelInterfaces() to enable entity resolution.
Composer Package
The composer.json must declare "type": "sylius-plugin" and require sylius/sylius or individual Sylius component packages. Register the bundle class in the extra.symfony.bundles section for Symfony Flex auto-configuration.
For complete plugin skeleton setup, entity registration, trait-based customization, and recipe-based installation, see references/plugin-architecture.md.
Resource Model
Sylius ResourceBundle provides a uniform pattern for managing domain entities. Every resource (product, order, channel, payment method) is registered with the ResourceBundle, which auto-generates a controller, repository, factory, form type, and routing for CRUD operations.
Defining a Resource
Register resources in the bundle configuration under the sylius_resource key:
sylius_resource:
resources:
app.bonus_points:
classes:
model: App\Entity\BonusPoints
interface: App\Entity\BonusPointsInterface
repository: App\Repository\BonusPointsRepository
factory: App\Factory\BonusPointsFactory
form:
default: App\Form\Type\BonusPointsType
This single declaration gives the resource:
- A CRUD controller (
sylius.controller.bonus_points) - A repository service (
app.repository.bonus_points) - A factory service (
app.factory.bonus_points) - Automatic routing for admin CRUD pages (when combined with grid configuration)
Entity Interfaces and Resolution
Sylius uses Doctrine's ResolveTargetEntityListener to decouple entities from concrete classes. Every entity implements an interface, and the ResourceBundle maps the interface to the concrete class. This allows plugins to swap entity implementations without modifying dependent code. When extending a core entity (e.g., adding fields to Product), create a trait with the additional properties and apply it to the project-level entity class.
Factories
Use Sylius factories (not new Entity()) to instantiate resources. Factories ensure that required defaults are set and that the correct class is instantiated (respecting entity resolution). Decorate the factory service to customize default values for new resources.
Extension Mechanisms
Sylius provides multiple extension points rooted in Symfony's architecture.
Service Decoration
Override any Sylius service by decorating it in the DI container. Decoration wraps the original service, allowing the plugin to add behavior before or after the original method call:
services:
app.calculator.decorated_shipping:
class: App\Calculator\DecoratedShippingCalculator
decorates: sylius.shipping_calculator.flat_rate
arguments:
- '@.inner'
Form Extensions
Extend existing form types (product, order, customer) using Symfony form type extensions. This adds fields to admin forms without overriding the entire form class:
class ProductTypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [ProductType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('customField', TextType::class);
}
}
Template Events (Twig Blocks)
Sylius defines template events (also called Sylius template blocks) at strategic points in admin and shop templates. Inject content into these locations by registering blocks in configuration:
sylius_ui:
events:
sylius.shop.product.show.content:
blocks:
bonus_points:
template: '@MyPlugin/Shop/Product/_bonusPoints.html.twig'
priority: 10
Event Listeners
Listen to Symfony events dispatched by Sylius for lifecycle hooks (pre-create, post-update, pre-delete). ResourceBundle dispatches events following the pattern sylius.<resource>.<action> (e.g., sylius.product.pre_create).
For complete coverage of Symfony DI patterns, compiler passes, tagged services, grid configuration, and menu builders, see references/service-container.md.
For admin panel customization, Sonata blocks, Twig block overrides, JavaScript widgets, and form themes, see references/admin-and-twig.md.
API Platform Integration
Sylius 1.13+ and Sylius 2.0 use API Platform as the primary API layer, replacing the legacy FOSRestBundle-based API. API Platform provides automatic REST and GraphQL endpoints for Sylius resources with serialization, validation, pagination, and filtering.
Resource Declaration
Expose a Sylius entity through API Platform by adding the #[ApiResource] attribute (or XML/YAML configuration):
#[ApiResource(
operations: [
new GetCollection(),
new Get(),
new Post(),
],
normalizationContext: ['groups' => ['bonus_points:read']],
denormalizationContext: ['groups' => ['bonus_points:write']],
)]
class BonusPoints implements BonusPointsInterface
{
#[Groups(['bonus_points:read'])]
private int $points;
}
Serialization Groups
Control which fields appear in API responses using serialization groups. Sylius defines standard groups (shop:read, admin:read, admin:write) that plugins should follow for consistency.
Custom Operations and Filters
Add custom API operations for business logic that does not map to standard CRUD. Use API Platform data providers and data persisters to customize how resources are fetched and saved.
For complete API Platform integration patterns including custom resources, filters, data providers, and endpoint customization, see references/api-platform.md.
Development Setup
Plugin Skeleton
The official sylius/plugin-skeleton provides a ready-to-use development environment:
composer create-project sylius/plugin-skeleton my-sylius-plugin
cd my-sylius-plugin
composer install
(cd tests/Application && bin/console sylius:install --no-interaction)
(cd tests/Application && bin/console server:start)
The skeleton includes a test Sylius application in tests/Application/ that loads the plugin bundle. This allows running the full Sylius application with the plugin active for manual testing and Behat acceptance tests.
Sylius Standard Project
For end-to-end testing against a real Sylius store, install Sylius Standard:
composer create-project sylius/sylius-standard my-shop
cd my-shop
bin/console sylius:install
symfony serve
Require the plugin as a local Composer path repository during development:
{
"repositories": [
{ "type": "path", "url": "../my-sylius-plugin" }
],
"require": {
"acme/sylius-bonus-points-plugin": "dev-main"
}
}
Database and Fixtures
Set up the test database with Sylius fixtures for a realistic development environment:
bin/console doctrine:database:create --env=dev
bin/console doctrine:schema:create --env=dev
bin/console sylius:fixtures:load default --no-interaction --env=dev
The default fixture suite creates sample channels, currencies, locales, products, customers, and orders. Create plugin-specific fixture suites for data that the plugin needs during development.
Debug Tools
Enable Symfony Profiler and Web Debug Toolbar for development. Key debug panels for Sylius plugin development:
- Doctrine -- Inspect SQL queries generated by resource operations. Watch for N+1 queries when loading related entities.
- Events -- View all dispatched events to verify that plugin listeners trigger correctly.
- Security -- Debug authentication and authorization issues for admin and shop contexts.
- API Platform -- Inspect API request/response serialization, filters, and pagination.
Testing Stack
Sylius uses a three-tier testing approach:
- Behat + Mink -- Acceptance tests that drive the browser (or HTTP client) through complete user flows. Sylius provides extensive Behat contexts and page objects for admin and shop interactions.
- PHPSpec -- Unit-level specification tests for individual classes. Sylius core uses PHPSpec for all domain logic.
- PHPUnit -- Integration tests for database queries, service wiring, and API endpoints.
For detailed testing setup, fixtures, and Sylius test helpers, see references/testing-with-behat.md.
Anti-Patterns
Avoid these common mistakes in Sylius plugin development:
Overriding Controllers Directly
Never replace Sylius controllers with custom implementations. Use service decoration or event listeners to modify behavior. Direct controller replacement breaks when Sylius updates the controller signature.
Bypassing the Resource Layer
Never use $entityManager->persist() directly on Sylius resources. Use the resource controller flow or the repository service ($repository->add($resource)). Bypassing the resource layer skips events, validation, and authorization checks.
Hardcoding Entity Classes
Never reference concrete entity classes like Sylius\Component\Core\Model\Product directly in service definitions or DQL queries. Use the entity interface (ProductInterface) and let the resource bundle resolve the concrete class. Hardcoded classes break when the shop overrides the entity.
Copying Templates Instead of Extending
Never copy entire Sylius templates into the plugin to make small modifications. Use template events (Twig blocks) to inject content at specific points, or override only the specific block within a template. Copying templates creates maintenance burden when Sylius updates its templates.
Ignoring Channel Awareness
Sylius is multi-channel by design. Every resource query in shop context must be scoped to the current channel. Forgetting channel filtering exposes data from other channels and breaks multi-store setups.
Modifying Core Migrations
Never alter Sylius core migration files. Create separate plugin migrations for schema changes. Use Doctrine migration diffing against the plugin's entity mappings.
Tight Coupling to Sylius Internals
Avoid depending on Sylius internal classes (marked @internal or in Internal namespaces). These classes change between minor versions without notice. Depend only on public interfaces and services documented in the Sylius API.
Missing Translation Keys
Always provide translation files for every UI string. Hardcoded English strings in templates break multi-language stores. Use the trans Twig filter and Symfony translation component for all user-facing text. Provide at least English translations; document translation keys so shop owners can add their own translations.
Ignoring State Machine Transitions
Sylius uses the Winzou State Machine (Sylius 1.x) or Symfony Workflow (Sylius 2.0) for order, payment, and shipment lifecycle management. Never change entity statuses directly (e.g., $order->setState('completed')). Always apply state machine transitions through the state machine service so that guards, callbacks, and event listeners execute correctly.
Reference Files
- Plugin Architecture -- Plugin skeleton, bundle structure, configuration, resource registration, entity extension, trait-based customization, recipe-based installation
- Service Container -- Symfony DI in Sylius, service decoration, compiler passes, tagged services, form extensions, grid configuration, menu builders
- API Platform -- API Platform integration, custom resources, serialization groups, operations, filters, data providers, custom endpoints
- Admin and Twig -- Admin panel customization, Sonata blocks, template events, Twig block overrides, JavaScript widgets, grid customization, form themes
- Testing with Behat -- Behat/Mink for acceptance testing, PHPSpec for unit testing, PHPUnit integration, test fixtures, Sylius test helpers