Storage
The add-on stores file bytes on the same filesystem the platform uses for internal data. By default that means local disk, in internal_data/mc-downloads/ under your board root. To swap to S3, B2, R2, or SFTP, edit your config.php to remap the internal-data adapter.
There are no add-on-specific storage settings in the AdminCP options page. All storage configuration is handled at the platform level so it stays consistent with how your board handles attachments and avatars.
Default storage location
| Item | Value |
|---|---|
| On-disk path (default) | <board>/internal_data/mc-downloads/ |
| Layout | Files are spread across two-character buckets based on a hash of their content, which keeps directory listings small. |
Swapping the backend
Filesystem adapters are read from $config['fsAdapters'] in src/config.php. The add-on does not need to know which adapter is active; whatever the internal-data adapter points at is where files go.
Example: Amazon S3
$config['fsAdapters']['internal-data'] = function (
\XF\Container $c,
string $name
): \League\Flysystem\FilesystemAdapter
{
return new \League\Flysystem\AwsS3V3\AwsS3V3Adapter(
new \Aws\S3\S3Client([
'credentials' => [
'key' => 'YOUR_KEY',
'secret' => 'YOUR_SECRET',
],
'region' => 'us-east-1',
'version' => 'latest',
]),
'your-bucket-name',
'optional/path/prefix'
);
};
Every add-on that uses internal-data (core attachments, this add-on, the Resource Manager, and others) now writes to S3.
Example: SFTP
$config['fsAdapters']['internal-data'] = function (
\XF\Container $c,
string $name
): \League\Flysystem\FilesystemAdapter
{
return new \League\Flysystem\PhpseclibV3\SftpAdapter(
new \League\Flysystem\PhpseclibV3\SftpConnectionProvider(
'sftp.example.com',
'username',
'password',
null,
null,
22
),
'/remote/path'
);
};
Migrating between backends
There is no built-in migration command. To migrate:
- Put the site in maintenance mode.
- Copy the contents of the current
internal_data/mc-downloads/directory to the new backend, preserving paths. - Update the adapter in
config.phpto point at the new backend. - Test a download. Confirm it streams.
- Bring the site out of maintenance mode.
For larger sites, run an rsync or aws s3 sync once with the site live, a second sync at maintenance start, then swap the config.
Path safety
The add-on enforces three guards on every read or write to prevent path-traversal attacks:
- No
..segments. - No null bytes.
- No Windows-absolute paths.
These guards apply to every stored file path and to incoming external URL fetches.
Hash deduplication
When Deduplicate uploads by SHA256 hash (on the File policy tab) is on, an upload whose hash matches an existing stored file reuses the existing storage instead of writing a fresh copy. Two downloads can share the same underlying bytes. Hard-deleting a file checks the reference count before removing bytes from disk.
Turning hash dedup off means every upload writes a new copy even for identical files. You can toggle this safely; existing files are not affected by the change.