tmonier / sylius-audit-log-plugin
Audit log of admin changes (who changed what, when) for the Sylius 2 admin panel
Package info
github.com/thibmonier/sylius-audit-log-plugin
Type:sylius-plugin
pkg:composer/tmonier/sylius-audit-log-plugin
Requires
- php: ^8.2
- sylius/sylius: ^2.2
Requires (Dev)
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- sylius-labs/coding-standard: ^4.4
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4 || ^8.0
- symfony/css-selector: ^6.4 || ^7.4 || ^8.0
- symfony/debug-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/dom-crawler: ^6.4 || ^7.4 || ^8.0
- symfony/dotenv: ^6.4 || ^7.4 || ^8.0
- symfony/http-client: ^6.4 || ^7.4 || ^8.0
- symfony/intl: ^6.4 || ^7.4 || ^8.0
- symfony/runtime: ^6.4 || ^7.4 || ^8.0
- symfony/web-profiler-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/webpack-encore-bundle: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-11 07:42:21 UTC
README
Who changed what, when: an audit log of the changes made in the Sylius 2 back office.
A price dropped, a promotion started a day early, a product disappeared from a channel: the Sylius 2 admin cannot tell who did it. The audit plugins written for Sylius 1 were never ported.
This plugin records the creations, updates and deletions of the main Sylius resources with a field-level diff (old → new), the administrator who made them and the request they came from, and shows them in the admin. It is free and open source.
Features
| Need | What the plugin does |
|---|---|
| Know who changed a resource, and how | Every create / update / delete of the audited resources is recorded with a field-level diff (old → new), the date (UTC), the actor (administrator id + username snapshot, customer, command line or system), the source (admin panel, admin API, shop, shop API, command line) and the route or console command. |
| Search the trail | Configuration › Audit log page: a Sylius grid filtered by date range, resource (type and id), action, actor and actor type, request. Each entry has a page with a readable diff table. |
| See the history where you work | History card on the product (show and edit), order, customer and promotion pages: the last changes of the resource and of what it owns (variants, prices, coupons, payments, addresses...), with a link to the filtered grid. |
| Group the changes of one save | Every entry carries a request id (X-Request-Id header when present): "All changes of this request" lists everything one click changed. |
| Keep secrets out of the log | Passwords, salts, tokens, payment details and gateway configuration are masked: the log says that they changed, never their values. Long texts are truncated. |
| Respect the GDPR | Retention: tmonier:audit-log:purge deletes entries older than retention_days (365 by default). Erasure: tmonier:audit-log:anonymize-customer removes a customer's personal data from the trail. IP addresses are not stored by default (anonymised when enabled). |
| Audit your own entities | Add any Doctrine entity to the configuration; tag your own label or owner resolvers; listen to an event to enrich or skip entries. |
Also included: translations in English, French, German, Spanish, Italian, Dutch and Polish; Twig Hooks everywhere; MySQL, MariaDB and PostgreSQL support.
Audit log or timeline?
This plugin is a technical audit trail of the data changed in the back office, for every resource. The sibling plugin tmonier/sylius-timeline-plugin is a business view of an order's life (placed, paid, shipped, e-mails sent) with internal staff notes. They complement each other and can be installed together.
Screenshots
Screenshots go in docs/images/.
Requirements
| Version | |
|---|---|
| PHP | 8.2+ |
| Sylius | 2.2, 2.3 |
| Symfony | 6.4, 7.4, 8.0 |
Installation
-
Require the package:
composer require tmonier/sylius-audit-log-plugin
-
Register the bundle in
config/bundles.php(Symfony Flex does it automatically once a recipe exists):Tmonier\SyliusAuditLogPlugin\TmonierSyliusAuditLogPlugin::class => ['all' => true],
-
Import the configuration in
config/packages/tmonier_sylius_audit_log.yaml:imports: - { resource: "@TmonierSyliusAuditLogPlugin/config/config.yaml" }
-
Import the admin routes in
config/routes/tmonier_sylius_audit_log.yaml:tmonier_sylius_audit_log: resource: "@TmonierSyliusAuditLogPlugin/config/routes.yaml"
-
Run the migrations:
bin/console doctrine:migrations:migrate
-
Schedule the purge (cron, Symfony Scheduler...), for instance daily:
bin/console tmonier:audit-log:purge
Configuration
Every key is optional; the defaults are shown.
# config/packages/tmonier_sylius_audit_log.yaml tmonier_sylius_audit_log: enabled: true # false: record nothing (the admin pages stay available) sources: [admin, admin_api, cli] # also: shop, shop_api, other ignored_commands: [sylius:fixtures:load, doctrine:fixtures:load] # Merged with the default resources (see below). A resource is a Doctrine class or interface. resources: tax_category: # add one class: Sylius\Component\Taxation\Model\TaxCategoryInterface brand: # add one owned by another resource class: App\Entity\Brand owner: product # its entries show on the product history owner_property_path: product product: ignored_fields: [position] # extra ignored fields for one resource customer: false # stop auditing a default resource ignored_fields: [createdAt, updatedAt, lastLogin, emailCanonical, usernameCanonical, version] masked_fields: [password, plainPassword, salt, tokenValue, details, config] masked_field_patterns: ['/password/i', '/secret/i', '/token/i', '/salt/i', '/credential/i', '/api_?key/i'] max_value_length: 500 # longer texts are truncated with "…" store_ip: false anonymize_ip: true # 203.0.113.42 -> 203.0.113.0 when store_ip is on retention_days: 365 # default of tmonier:audit-log:purge; null keeps entries forever personal_resources: [customer, address] # fully anonymised on erasure personal_fields: [email, firstName, lastName, phoneNumber, street, city, postcode, customer, notes, ...] history_limit: 10 # entries shown in the history cards
Audited by default
| Resource | Owner (history card) |
|---|---|
| Products (with their translations) | — |
Variants (incl. stock onHand / onHold), channel prices |
product |
| Taxons | — |
| Promotions; their coupons, rules and actions | promotion |
| Catalog promotions; their scopes and actions | catalog promotion |
| Shipping methods, payment methods, tax rates, channels | — |
| Administrators, customers | — |
| Addresses | customer (address book) or order (order addresses) |
| Orders; their payments and shipments | order |
Translation changes are recorded on the translated resource (name (fr_FR)), many-to-many
changes as added / removed lists, references to other resources as a label and an id.
Only the admin, admin_api and cli sources are recorded by default: the shop changes
carts, stock and customers all the time, and those changes are not administration. Add shop
to sources to record them too. Fixtures loading is not recorded.
Commands
# Retention: delete entries older than retention_days, or a given age bin/console tmonier:audit-log:purge bin/console tmonier:audit-log:purge --older-than=90d --dry-run bin/console tmonier:audit-log:purge --older-than="6 months" # Right to erasure: by e-mail, or by id for a customer already deleted bin/console tmonier:audit-log:anonymize-customer jane@example.com --dry-run bin/console tmonier:audit-log:anonymize-customer 42 --email=jane@example.com
The anonymisation covers the entries of the customer, its addresses, its orders (with their payments, shipments and addresses) and the changes it made from the shop. Entries about a personal resource lose every value and their label; the other entries (orders...) lose the values of the personal fields. The IP address is removed. The administrators who made the changes and the dates stay: the trail remains usable.
Customisation
Labels. Implement Tmonier\SyliusAuditLogPlugin\Resolver\LabelResolverInterface; with
autoconfiguration it is tagged tmonier_audit_log.label_resolver (set a priority above 0 to
override the core labels).
Owners. Implement Tmonier\SyliusAuditLogPlugin\Resolver\OwnerResolverInterface (tag
tmonier_audit_log.owner_resolver) when a property path is not enough to find the parent of an
entity.
Entries. Listen to Tmonier\SyliusAuditLogPlugin\Event\AuditLogEntryEvent::PRE_WRITE
(tmonier_audit_log.entry.pre_write) to enrich an entry, rewrite its changes or skip it:
#[AsEventListener(event: AuditLogEntryEvent::PRE_WRITE)] public function __invoke(AuditLogEntryEvent $event): void { if ($event->getSubject() instanceof ProductVariantInterface && ['onHold'] === $event->getEntry()->getChangedFields()) { $event->skip(); } }
Entry class. The entry is a Sylius resource (tmonier_audit_log.audit_log_entry): replace
the model under tmonier_sylius_audit_log.models.audit_log_entry.classes.model to add fields;
they are written too.
Templates. Every admin block is a Twig hook: tmonier_audit_log.admin.audit_log_entry.*
for the entry page, tmonier_audit_log_history in the product, order, customer and promotion
hooks for the history cards.
How changes are recorded
A Doctrine listener reads the unit of work in onFlush (old values are still known) and writes
the entries in postFlush with plain DBAL inserts (new entities have their ids by then). The
inserts run in a savepoint of the caller's transaction when there is one. Writing never
flushes the entity manager again, so there is no recursion, and nested flushes keep their own
changes. Any error while recording is logged (channel tmonier_audit_log) and swallowed: the
audit log never breaks the change it records.
Limitations
- Changes made without the Doctrine ORM (DBAL or SQL queries, bulk DQL
UPDATE/DELETE) are not recorded. - Entries are written right after the database commit of the change: if PHP dies between the two, the change is kept and its entry is lost.
- One-to-many collections appear on their owner only when Doctrine schedules them (for instance a coupon added to a promotion); the child entity always has its own entry.
- Prices are stored as integers in the smallest currency unit (cents), as Sylius does.
- The date filter compares local dates with UTC dates.
- Sylius Plus RBAC is not integrated: every administrator can read the audit log.
Development
docker compose up -d mysql mailhog # or your own MySQL composer install (cd vendor/sylius/test-application && yarn install && yarn build) vendor/bin/console doctrine:database:create vendor/bin/console doctrine:migrations:migrate -n vendor/bin/console sylius:fixtures:load -n vendor/bin/console tmonier:audit-log:demo # demo entries (test application only) vendor/bin/phpunit --testsuite unit APP_ENV=test vendor/bin/phpunit --testsuite non-unit # needs a test database with fixtures vendor/bin/phpstan analyse vendor/bin/ecs check
License
MIT. See LICENSE.