Protecting uploaded files – private storage & signed links
Note: Available from version 2.5.0
Up to version 2.4.x, uploaded customer files lived under public/uploads/ and were served directly by the web server – without any check. Anyone who knew the path could retrieve the file. And that path appears in the customer's order confirmation, in the internal notification email and in the administration.
For print artwork that is usually harmless. As soon as your customers upload ID copies, medical certificates, prescriptions or engineering drawings, it no longer is.
From 2.5.0 you can have those files stored outside the public directory. They are then served only through a signed link that carries its own authorisation and expires after a period you define.
The three settings
You will find them in the plugin settings under File downloads.
| Setting | Default | Effect |
|---|---|---|
| Store uploads privately and use signed download links | on update: off, on fresh install: on | Determines where new uploads are stored |
| Download link lifetime in days | 90 | How long a link in an already sent email keeps working. 0 = unlimited |
| Keep serving legacy public paths | on | Keeps links working that were sent before the changeover |
Why the lifetime rarely gets in the way
The lifetime applies only to links in emails that have already been sent. In the customer account, on the order completion page and in the administration the link is regenerated on every request and is therefore always valid.
So only two cases are affected: guest customers digging out an old order confirmation, and staff opening an archived internal email – and the latter can also find the file in the administration.
90 days cover the 14-day right of withdrawal plus the usual complaint period with room to spare. If you work with long-running processes – custom manufacturing, construction drawings – raise the lifetime or set it to 0.
The changeover step by step
Every step can be undone individually.
1. Install the update
Nothing changes at first. On an update, private storage stays switched off so that your existing email templates keep working unchanged.
2. Switch over the email templates
In your templates, replace the hand-built path with the link the plugin supplies:
Before
<a href="{{ url('frontend.home.page') }}uploads/final/{{ upload.qquuid }}/{{ upload.fileName }}">{{ upload.fileName }}</a>After
<a href="{{ upload.downloadUrl }}">{{ upload.fileName }}</a>This works immediately and does not change the behaviour yet: as long as private storage is off, downloadUrl returns exactly the public path your template used to build itself.
Both templates are affected: the order confirmation to the customer and the internal email to your team.
Why this is the most important step: with
downloadUrl, your templates never assemble the path themselves again. Future changes to the storage location stay invisible to you – exactly the problem this changeover is causing right now will not happen a second time.
3. Switch on private storage
From now on, new uploads land outside the public directory and are served only through signed links. Existing files stay where they are and remain reachable.
4. Move existing files
bin/console futi:uploads:migrate --dry-runThe dry run shows how many files are affected and how much storage they occupy, without changing anything. If you are happy with it:
bin/console futi:uploads:migrateThe command works in batches, skips files that have already been moved, and can be aborted and restarted at any time.
As long as files remain in the public directory, a notice in the plugin settings points this out.
Why switching does not break any links
The setting only controls where new uploads are stored. Delivery always follows the actual storage location of each individual file:
- File is stored privately → signed link, regardless of the setting
- File is stored publicly → public path
That is why you can switch the setting on and off again without a single link pointing nowhere – not even after a migration.
Prerequisite
Private storage uses Shopware's private filesystem. In the default configuration that is a local directory (files/) outside the document root – nothing to do there.
If you have switched the private filesystem to remote storage (S3 or similar), please leave the setting switched off.
When a link has expired
Customers do not see an error page but a notice pointing them to the order overview in their customer account. From there the file is reachable at any time, because the link is regenerated on every request.
Was this page helpful?
Support
Rented Plugins (Shopware Store)
For support with plugins rented from the Shopware Store, please open a support ticket in your Shopware account.
Create Shopware Ticket