jkindly / sylius-digital-product-plugin
Sylius Digital Product Plugin
Package info
github.com/jkindly/SyliusDigitalProductPlugin
Type:sylius-plugin
pkg:composer/jkindly/sylius-digital-product-plugin
Requires
- php: ^8.2
- league/flysystem-bundle: ^3.6
- sylius/sylius: ^2.0.7
- symfony/serializer: ^6.4 || ^7.4
- symfony/yaml: ^6.4 || ^7.4
Requires (Dev)
- behat/behat: ^3.16
- 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.3
- 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/phpstan: ^1.12
- phpstan/phpstan-doctrine: ^1.3
- phpstan/phpstan-webmozart-assert: ^1.2
- phpunit/phpunit: ^10.5
- roave/security-advisories: dev-latest
- robertfausk/behat-panther-extension: ^1.1
- sylius-labs/coding-standard: ^4.4
- sylius-labs/suite-tags-extension: ~0.2
- sylius/sylius-rector: ^2.0
- 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
README
Sell digital products in Sylius with uploaded files, external URLs, download limits, and post-payment delivery.
Overview
This plugin adds digital product support to Sylius 2.x.
It lets you:
- mark product variants as digital
- attach multiple files to a variant
- scope files per channel
- use built-in file types: uploaded files and external URLs
- configure download limits and availability windows
- send download links automatically after payment
- let customers download files from the storefront order area
- resend download emails from the admin panel
- upload large files in chunks
For uploaded files, the plugin copies the original product file into an order-specific storage when payment is completed. This keeps customer downloads independent from later catalog changes.
Requirements
- PHP 8.2+
- Symfony 6.4 or 7.4
- Sylius 2.x
- League Flysystem Bundle 3.x
- Node.js 20+ for building Sylius Standard frontend assets
Installation
1. Require the plugin
composer require jkindly/sylius-digital-product-plugin
2. Register the bundle
Add the plugin bundle to config/bundles.php if it is not registered automatically:
<?php return [ Jkindly\SyliusDigitalProductPlugin\SyliusDigitalProductPlugin::class => ['all' => true], ];
3. Import the plugin routes
sylius_digital_product_admin: resource: "@SyliusDigitalProductPlugin/config/routes/admin.yaml" prefix: /admin sylius_digital_product_shop: resource: "@SyliusDigitalProductPlugin/config/routes/shop.yaml"
4. Extend your Sylius models
The plugin expects your application models to implement its interfaces and use its traits.
Product variant
Your product variant model should:
- implement
Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductVariantInterface - use
Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductFilesAwareTrait - use
Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductVariantSettingsAwareTrait - add the matching Doctrine relations for:
DigitalProductVariantSettingsDigitalProductFile
Example:
<?php declare(strict_types=1); namespace App\Entity\Product; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Sylius\Component\Core\Model\ProductVariant as BaseProductVariant; use Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductFile; use Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductVariantInterface; use Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductVariantSettings; use Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductVariantSettingsInterface; use Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductFilesAwareTrait; use Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductVariantSettingsAwareTrait; #[ORM\Entity] #[ORM\Table(name: 'sylius_product_variant')] class ProductVariant extends BaseProductVariant implements DigitalProductVariantInterface { use DigitalProductFilesAwareTrait; use DigitalProductVariantSettingsAwareTrait; #[ORM\OneToOne(targetEntity: DigitalProductVariantSettings::class, mappedBy: 'productVariant', cascade: ['persist', 'remove'], orphanRemoval: true)] protected ?DigitalProductVariantSettingsInterface $digitalProductVariantSettings = null; #[ORM\OneToMany(targetEntity: DigitalProductFile::class, mappedBy: 'productVariant', cascade: ['persist', 'remove'], orphanRemoval: true)] protected Collection $files; public function __construct() { parent::__construct(); $this->initializeFilesCollection(); } }
Channel
Your channel model should:
- implement
Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductChannelInterface - use
Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductFileChannelSettingsAwareTrait - add the relation for
DigitalProductChannelSettings
Order
Your order model should:
- implement
Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductOrderInterface - use
Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductOrderAwareTrait
Order item
Your order item model should:
- implement
Jkindly\SyliusDigitalProductPlugin\Entity\DigitalProductOrderItemInterface - use
Jkindly\SyliusDigitalProductPlugin\Entity\Trait\DigitalProductFilesAwareTrait - add the relation for
DigitalProductOrderItemFile
The test application in tests/TestApplication/src/Entity/ shows the full working setup.
5. Point Sylius resources to your extended models
Example:
sylius_product: resources: product_variant: classes: model: App\Entity\Product\ProductVariant sylius_channel: resources: channel: classes: model: App\Entity\Channel\Channel sylius_order: resources: order: classes: model: App\Entity\Order\Order order_item: classes: model: App\Entity\Order\OrderItem
6. Run migrations
The plugin prepends its Doctrine migrations automatically. Run:
bin/console doctrine:migrations:migrate
7. Configure mailer, storage, and Twig hooks if needed
The bundle automatically registers:
- Sylius Mailer configuration for the digital download email
- Flysystem storages for product files, order files, and upload chunks
- Twig hooks for admin and shop UI integration
- serializer and validator mapping
You only need extra configuration if you want to override the defaults.
Configuration
Root key:
sylius_digital_product:
Available options:
sylius_digital_product: uploaded_file: delete_from_storage_on_remove: false chunk_size: 5242880 product_files_path: null order_files_path: null chunks_path: null
Default storage directories:
var/uploads/product_filesvar/uploads/order_filesvar/uploads/tmp/chunks
Built-in File Types
The plugin provides two file types out of the box:
uploaded_file
A physical file stored through Flysystem.external_url
A redirect to a remote URL.
Each file type has its own:
- DTO
- form type
- validator
- serializer
- provider
- response generator
How It Works
Admin side
- channel forms expose default digital-product settings
- product and variant forms expose digital settings and file collections
- uploaded files can be sent directly or through chunked upload
- admins can download uploaded files for preview
Payment flow
When the workflow.sylius_order_payment.completed.pay event is triggered, the plugin:
- creates
DigitalProductOrderItemFilerecords for every digital file in the order - copies uploaded files into order-specific storage
- calculates download limits and expiration dates
- dispatches a message that sends the digital download email
Shop side
Customers receive download links after payment and can also access files from the order view.
The public download route uses a UUID token:
/download/{uuid}
Before returning the response, the plugin:
- checks whether the file exists for the order
- verifies the download limit
- verifies the availability window
- increments the download count
- returns either a streamed file download or a redirect, depending on file type
Guest customers are supported because the UUID acts as the download token.
Operational Notes
Resend download email
The admin order page includes an action for resending the digital download email after the order has been paid.
Cleanup abandoned chunks
Large uploads can leave temporary chunk directories behind. Use:
bin/console sylius:digital-product:cleanup-chunks
Options:
--hours=24to remove only old chunks--forceto remove all chunk directories
Extending the Plugin
The file type system is extensible. To add a custom type, create and register:
- a DTO implementing
FileDtoInterface - a form type extending
AbstractFileType - a data transformer for DTO <-> array conversion
- a serializer for the configuration payload
- a response generator
- a provider implementing
FileProviderInterface
Register the provider, serializer, and response generator with the plugin tags defined in config/services/.
Development
Test application setup
composer install vendor/bin/console doctrine:database:create vendor/bin/console doctrine:migrations:migrate -n vendor/bin/console sylius:fixtures:load -n (cd vendor/sylius/test-application && yarn install) (cd vendor/sylius/test-application && yarn build) vendor/bin/console assets:install
Tests
vendor/bin/phpunit vendor/bin/behat vendor/bin/phpstan analyse -c phpstan.neon -l max src vendor/bin/ecs check
For JavaScript Behat scenarios, start a browser driver and the Symfony test server first, as in the test application workflow already used in this repository.
License
This plugin is released under the MIT License.