fof / upload
The file upload extension for the Flarum forum with insane intelligence.
Fund package maintenance!
Website
Installs: 164 544
Dependents: 14
Suggesters: 4
Security: 1
Stars: 187
Watchers: 6
Forks: 100
Open Issues: 49
Type:flarum-extension
pkg:composer/fof/upload
Requires
- ext-json: *
- enshrined/svg-sanitize: ^0
- flarum/core: ^2.0.0
- guzzlehttp/guzzle: ^6.0 || ^7.0
- ramsey/uuid: ^3.5.2 || ^4
- softcreatr/php-mime-detector: ^4.0
Requires (Dev)
- flarum/gdpr: ^2.0.0
- flarum/phpstan: ^2.0.0
- flarum/testing: ^2.0.0
- league/flysystem-aws-s3-v3: ^3.31
- overtrue/flysystem-qiniu: ^3.2
Suggests
- league/flysystem-aws-s3-v3: Uploads to AWS S3 using API version 3.
- overtrue/flysystem-qiniu: Uploads to QiNiu using API.
Replaces
- 2.x-dev
- 2.0.0-beta.2
- 2.0.0-beta.1
- 1.x-dev
- 1.8.9
- 1.8.8
- 1.8.7
- 1.8.6
- 1.8.5
- 1.8.4
- 1.8.3
- 1.8.2
- 1.8.1
- 1.8.0
- 1.7.1
- 1.7.0
- 1.6.1
- 1.6.0
- 1.5.8
- 1.5.7
- 1.5.6
- 1.5.5
- 1.5.4
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.5.0-beta.2
- 1.5.0-beta.1
- 1.4.7
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.3-beta.3
- 1.3.3-beta.2
- 1.3.3-beta
- 1.3.2
- 1.3.1
- 1.3.0
- 1.3.0-beta.1
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.0
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- 0.14.0
- 0.14.0-beta.1
- 0.14.0-beta
- 0.13.1
- 0.13.0
- 0.12.10
- 0.12.9
- 0.12.8
- 0.12.7
- 0.12.6
- 0.12.5
- 0.12.4
- 0.12.3
- 0.12.2
- 0.12.1
- 0.12.0
- 0.11.3
- 0.11.2
- 0.11.1
- 0.11
- 0.10.1
- 0.10.0
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8.5
- 0.8.4
- 0.8.3
- 0.8.2
- 0.8.1
- 0.8.0
- 0.7.1
- 0.7.0
- 0.7.0-beta.2
- 0.7.0-beta.1
- 0.6.0
- 0.6.0-beta
- 0.5.7
- 0.5.6
- 0.5.5
- 0.5.4
- 0.5.3
- 0.5.2
- 0.5.1
- 0.5.0
- 0.4.13
- 0.4.12
- 0.4.11
- 0.4.10
- 0.4.9
- 0.4.8
- 0.4.7
- 0.4.6
- 0.4.5
- 0.4.4
- 0.4.3
- 0.4.2
- 0.4.1
- 0.4.0
- 0.3.8
- 0.3.7
- 0.3.6
- 0.3.5
- 0.3.4
- 0.3.3
- 0.3.2
- 0.3.1
- 0.3.0
- 0.2.1
- 0.2.0
- 0.1.3
- 0.1.2
- 0.1.1
- 0.1.0
- dev-im/fix-paste-clipboard
- dev-im/fix-image-preview-empty-src
- dev-im/fix-post-delete-cascade
- dev-im/fix-313-fileinfo-warning
- dev-im/fix-adapter-key-mismatch
- dev-im/image-thumbnails-webp
- dev-im/image-dimensions-layout-shift
- dev-im/template-tests-and-alt-fix
- dev-im/watermark-improvements
- dev-im/per-mime-permissions
- dev-im/1x-fix-374-file-mapping-and-cleanup
- dev-im/fix-374-file-mapping-and-cleanup
- dev-im/2.x
- dev-dependabot/npm_and_yarn/js/webpack-5.104.1
- dev-dependabot/npm_and_yarn/js/lodash-4.17.23
- dev-di/permission-issues
- dev-im/svg
- dev-cw/texteditor-reusability
- dev-dk/image-in-private-discussions
- dev-cw/mime-inspector
- dev-dk/edit-modal
- dev-dw/fix-upload-button-size
- dev-beta13
- dev-dk/beta-14
This package is auto-updated.
Last update: 2026-02-22 22:05:44 UTC
README
An extension that handles file uploads intelligently for your forum.
Features
- For images:
- Auto watermarks with proportional scaling, opacity and padding controls.
- Auto resizing.
- Dimension storage (width × height) for JPEG, PNG, and GIF — prevents layout shifts when images lazy-load.
- WebP thumbnail generation at upload time — reduces bandwidth; clicking the thumbnail opens the original in a new tab.
- Animated GIF support for resize operations.
- Mime type to upload adapter mapping.
- Whitelisting mime types.
- Uploading on different storage services (local, imgur, AWS S3 for instance).
- Drag and drop uploads.
- Uploading multiple files at once (button and drag and drop both support this).
- Easily extendable, the extension heavily relies on Events.
- Extender interface to disable or force particular adapters (see below)
Installation
Install manually:
composer require fof/upload:"*"
Updating
composer require fof/upload:"*"
php flarum migrate
php flarum cache:clear
Configuration
Enable the extension, a new tab will appear on the left hand side. This separate settings page allows you to further configure the extension.
On new installations, a pre-defined regex will be inserted for you that enables image uploads, restricted to safe image types. We now include SVG as safe, due to our SVG sanitization method. Default image types allowed are:
- JPEG
- PNG
- GIF
- WebP
- AVIF
- BMP
- TIFF
- SVG
The regex for these types is ^image\/(jpeg|png|gif|webp|avif|bmp|tiff|svg\+xml)$, and can be modified as required. We STRONGLY discourage the use of a wildcard such as ^image\/.*, as this could introduce vulnerabilities in the uploaded files. Versions of fof/upload prior to 1.8.0 used this as default, and is considered insecure.
Make sure you configure the upload permission on the permissions page as well.
Per-Mime-Type Permission Scoping
Each mime type row can be given an optional Permission label (e.g. Images, Videos, Documents). When a label is set, FoF Upload automatically:
- Creates a dedicated permission in the Flarum permission grid — e.g.
Upload Images— scoped to the Start section. - Defaults that permission to the Member group on first use, preserving existing access.
- Enforces AND logic: a user must hold both the base
Upload filespermission and the mime-specific permission to upload that file type.
Use cases:
- Allow Members to upload images but restrict video uploads to Staff.
- Require a "Trusted" group role to upload ZIP archives or executable files.
- Mix restricted and unrestricted types freely — mime types without a permission label require only the base upload permission (fully backwards-compatible).
How to configure:
- Go to Admin → Extensions → FoF Upload.
- In the Mime Types section, enter a label in the Permission label field for any type you want to restrict (e.g.
Images). - Save settings — the permission appears immediately in Admin → Permissions under the Start section.
- Adjust which groups have that permission as needed.
Watermark Configuration
When Watermark images is enabled, FoF Upload stamps every uploaded JPEG or PNG with a watermark image. Three settings let you control how the watermark looks:
| Setting | Default | Description |
|---|---|---|
| Watermark size (% of image width) | 25 |
The watermark is scaled so its width equals this percentage of the uploaded image's width. A value of 25 means the watermark will always be one-quarter the width of the image, regardless of upload dimensions. |
| Watermark opacity (0–100) | 100 |
Opacity of the placed watermark. 100 is fully opaque; 0 is invisible. |
| Padding from edge (px) | 10 |
Inward pixel offset from the corner or edge set by the Position field. Set to 0 to place the watermark flush against the edge. |
How to configure:
- Go to Admin → Extensions → FoF Upload.
- Enable Watermark images and upload your watermark file.
- Choose a Position (e.g.
bottom-right). - Adjust size %, opacity, and padding to taste.
- Save — the settings are applied to every new image upload.
Backwards compatibility: Existing installs that used watermarks before these settings were introduced will use the defaults on next upload: 25% width, 100% opacity, 10 px padding. Set padding to
0to restore the previous flush-edge behaviour.
Image Dimensions & Layout Shift Prevention
FoF Upload automatically stores the width and height (in pixels) of every JPEG, PNG, and GIF processed at upload time. These are recorded after any resizing or EXIF orientation correction is applied, so they always reflect the final image as stored.
The stored dimensions are injected as width and height HTML attributes on the <img> tag rendered by the Image Preview template. Modern browsers use these attributes to reserve the correct amount of layout space before the image has loaded, eliminating Cumulative Layout Shift (CLS) in threads that contain lazy-loaded images.
No configuration is required — this happens automatically for all new JPEG, PNG, and GIF uploads. Images uploaded before this feature was added will not have stored dimensions; they continue to render without width/height attributes and are unaffected.
GIF support: Animated GIF uploads now also benefit from the Resize images setting. Watermarks are intentionally not applied to GIFs to avoid palette quality degradation.
Image Thumbnails
FoF Upload can generate a downscaled thumbnail at upload time to significantly reduce bandwidth for large-image forums. The original file is always kept intact.
When enabled, the Image Preview template displays the thumbnail as the visible image and wraps it in a link to the original — clicking the image opens the full-resolution file in a new tab.
How it works
- After the image is processed (resize, watermark, orientation), a copy is scaled down to a configurable maximum width (default: 1000 px).
- The thumbnail is encoded as WebP by default (~30% smaller than JPEG at equivalent quality) or in the original format if WebP is disabled.
- The thumbnail is stored alongside the original in the same storage backend (Local, S3, Qiniu). Imgur uploads are excluded — Imgur manages its own thumbnails.
- The thumbnail path is stored in the database. The full URL is derived from the live storage hostname at render time (same as the main file URL), so CDN domain changes are reflected automatically without re-processing.
Settings
| Setting | Default | Description |
|---|---|---|
| Generate thumbnails on upload | On | Master toggle. Disable to revert to full-resolution images in the Image Preview template. |
| Encode thumbnails as WebP | On | Use WebP encoding for the thumbnail. Disable to use the original image format (JPEG/PNG/GIF). |
| Thumbnail max width (px) | 1000 |
Thumbnails are scaled down so neither dimension exceeds this value. Images smaller than this are not upscaled. |
Backwards compatibility
- Old posts: Existing posts have no
thumbnail_pathstored. The formatter falls back to the original full-resolution URL — no change in appearance. - Imgur uploads: Thumbnails are not generated (Imgur handles image hosting directly).
- Private-shared files: Thumbnails are not generated for private files.
Storage Configuration
FoF Upload supports configuration via both the admin panel (database settings) and environment variables. Environment variables take precedence over database settings when configured.
Environment Variable Configuration
You can configure storage adapters using environment variables, which is particularly useful for:
- Docker/containerized deployments
- CI/CD pipelines
- Multi-environment setups (dev/staging/production)
- Keeping credentials out of the database
AWS S3 / S3-Compatible Storage
To configure S3 via environment variables, set all four required variables:
# Required (all 4 must be set) FOF_UPLOAD_AWS_S3_KEY=your-access-key FOF_UPLOAD_AWS_S3_SECRET=your-secret-key FOF_UPLOAD_AWS_S3_BUCKET=your-bucket-name FOF_UPLOAD_AWS_S3_REGION=us-east-1 # Optional FOF_UPLOAD_AWS_S3_ACL=public-read # Object ACL (public-read, private, etc.) FOF_UPLOAD_AWS_S3_ENDPOINT=https://s3.example.com # For S3-compatible services (MinIO, Wasabi, etc.) FOF_UPLOAD_AWS_S3_PATH_STYLE_ENDPOINT=true # Required for MinIO and some S3-compatible services FOF_UPLOAD_AWS_S3_CUSTOM_URL=https://cdn.example.com # Custom domain for your bucket FOF_UPLOAD_CDN_URL=https://cdn.example.com # CDN URL for serving files
IAM Role Authentication (EC2/ECS/EKS):
For environments with IAM roles (EC2 instances, ECS tasks, EKS pods), you can omit credentials:
# Required for IAM mode FOF_UPLOAD_AWS_S3_BUCKET=your-bucket-name FOF_UPLOAD_AWS_S3_REGION=us-east-1 FOF_UPLOAD_AWS_S3_USE_IAM=true # Enable IAM role authentication # Optional (same as above) FOF_UPLOAD_AWS_S3_ACL=public-read FOF_UPLOAD_CDN_URL=https://cdn.example.com
When FOF_UPLOAD_AWS_S3_USE_IAM=true, credentials are not required and the AWS SDK will automatically use the instance/pod IAM role.
Important Notes:
- Traditional mode: All 4 variables (
KEY,SECRET,BUCKET,REGION) must be set - IAM mode: Only
BUCKET,REGION, andUSE_IAM=trueare required - If any required variable is missing, the extension falls back to database settings
- Environment variables always override database settings when fully configured
S3-Compatible Services
LocalStack (local development):
FOF_UPLOAD_AWS_S3_KEY=test
FOF_UPLOAD_AWS_S3_SECRET=test
FOF_UPLOAD_AWS_S3_BUCKET=uploads
FOF_UPLOAD_AWS_S3_REGION=us-east-1
FOF_UPLOAD_AWS_S3_ENDPOINT=http://localhost:4566
FOF_UPLOAD_AWS_S3_PATH_STYLE_ENDPOINT=true # Required for LocalStack!
MinIO (self-hosted):
FOF_UPLOAD_AWS_S3_KEY=minioadmin
FOF_UPLOAD_AWS_S3_SECRET=minioadmin
FOF_UPLOAD_AWS_S3_BUCKET=uploads
FOF_UPLOAD_AWS_S3_REGION=us-east-1
FOF_UPLOAD_AWS_S3_ENDPOINT=https://minio.example.com
FOF_UPLOAD_AWS_S3_PATH_STYLE_ENDPOINT=true # Required for MinIO!
DigitalOcean Spaces:
FOF_UPLOAD_AWS_S3_KEY=your-spaces-key FOF_UPLOAD_AWS_S3_SECRET=your-spaces-secret FOF_UPLOAD_AWS_S3_BUCKET=your-space-name FOF_UPLOAD_AWS_S3_REGION=nyc3 FOF_UPLOAD_AWS_S3_ENDPOINT=https://nyc3.digitaloceanspaces.com
Wasabi:
FOF_UPLOAD_AWS_S3_KEY=your-wasabi-key FOF_UPLOAD_AWS_S3_SECRET=your-wasabi-secret FOF_UPLOAD_AWS_S3_BUCKET=your-bucket FOF_UPLOAD_AWS_S3_REGION=us-east-1 FOF_UPLOAD_AWS_S3_ENDPOINT=https://s3.wasabisys.com
Backblaze B2:
FOF_UPLOAD_AWS_S3_KEY=your-key-id FOF_UPLOAD_AWS_S3_SECRET=your-application-key FOF_UPLOAD_AWS_S3_BUCKET=your-bucket FOF_UPLOAD_AWS_S3_REGION=us-west-004 FOF_UPLOAD_AWS_S3_ENDPOINT=https://s3.us-west-004.backblazeb2.com
Local Storage with CDN
For local storage with a CDN in front:
FOF_UPLOAD_CDN_URL=https://cdn.example.com
Configuration Priority
- Environment Variables (highest priority - when all required vars are set)
- Database Settings (admin panel configuration)
- Defaults (null values if neither is configured)
Mimetype regular expression
Regular expressions allow you a lot of freedom, but they are also very difficult to understand. Here are some pointers, but feel free to ask for help on the official Flarum forums, or check out regex101.com where you can interactively build and test your regex pattern.
In case you want to allow all regular file types including video, music, compressed files and images, use this:
(video\/(3gpp|mp4|mpeg|quicktime|webm))|(audio\/(aiff|midi|mpeg|mp4))|(image\/(gif|jpeg|png))|(application\/(x-(7z|rar|zip)-compressed|zip|arj|x-(bzip2|gzip|lha|stuffit|tar)|pdf))
A mimetype consists of a primary and secondary type. The primary type can be image, video and application for instance.
The secondary is like a more detailed specification, eg png, pdf etc. These two are divided by a /, in regex you have to escape this character by using: \/.
Disable or Force a particular adapter
In some circumstances, you may wish to either disable an adapter, or force the use of one. This is set in your root extend.php file.
For example, you may disable imgur
(new FoF\Upload\Extend\Adapters())
->disable('imgur'),
Chaining of multiple commands is also possible:
(new FoF\Upload\Extend\Adapters())
->disable('imgur')
->disable('aws-s3'),
You may also force an adapter:
(new FoF\Upload\Extend\Adapters())
->force('imgur'),
Adapter names currently available:
localimgurqiniuaws-s3
Commands
MapFilesCommand
The php flarum fof:upload command helps you keep file storage clean by mapping uploaded files to
the posts they appear in, and removing files that were never used in any post (e.g. uploaded by a
user who never submitted their draft, or by spammers abusing the upload endpoint).
How matching works
When a post is saved or edited, files are automatically linked to it based on what appears in the
post content. The command's --map flag lets you rebuild these associations in bulk — useful after
an import, a migration, or if associations were lost.
Matching looks for the file's URL or UUID in post content, so all built-in templates are covered:
| Template | What appears in post content |
|---|---|
| Default File Download | [upl-file uuid=… size=…]name[/upl-file] — UUID only |
| Image Preview | [upl-image-preview uuid=… url=…] — both |
| Image | [upl-image uuid=… url=…] — both |
| Text Preview | [upl-text-preview uuid=… url=…] — both |
| Just URL | raw URL |
| Markdown Image |  |
| BBCode Image | [URL=…][IMG]…[/IMG][/URL] |
Note: Shared files (uploaded via the shared file manager) are intentionally not associated with individual posts and are never removed by cleanup.
Options
| Option | Description |
|---|---|
--map |
Scan all posts and link files to the posts where they appear. Safe to run at any time. |
--cleanup |
Delete files that have no post associations and were uploaded before the cutoff date. Always run --map first. |
--cleanup-before=DATE |
Set the cutoff date for cleanup. Any date string accepted by PHP's strtotime works: "yesterday", "1 week ago", "2024-01-01", "now". Defaults to 24 hours ago. |
--force |
Skip per-file confirmation prompts. Use with caution. |
Examples
Map files only (no deletions):
php flarum fof:upload --map
Map and clean up files uploaded more than a month ago that have no post association — with per-file confirmation:
php flarum fof:upload --map --cleanup --cleanup-before="1 month ago"
Same, but skip confirmation prompts (suitable for a cronjob):
php flarum fof:upload --map --cleanup --cleanup-before="1 month ago" --force
Recommended workflow
Always run
--mapbefore--cleanup. Without mapping first, files that are genuinely in use may appear orphaned and be deleted.
- Run
--mapfirst to rebuild file-to-post associations. - Review what would be deleted by running
--cleanupwithout--force(you will be prompted per file). - Once satisfied, add
--forcefor unattended runs.
For ongoing maintenance, a daily cronjob is a sensible setup:
# Remove files older than 24 hours (the default) that are not in any post
php flarum fof:upload --map --cleanup --force
BackfillImageDimensionsCommand
The php flarum fof:upload:backfill-dimensions command retroactively stores image_width and image_height for existing JPEG, PNG, and GIF uploads that were created before the dimension-storage feature was introduced.
The command downloads each image (using whatever storage backend the file was uploaded to — local, S3, CDN, etc.), reads its dimensions with Intervention Image, and saves them to the database. Images that already have dimensions stored are skipped automatically.
Options
| Option | Description |
|---|---|
--chunk=N |
Process files in batches of N (default: 100). Reduce this if memory is a concern. |
--dry-run |
Print how many images would be processed without making any changes. |
Examples
Preview how many images need backfilling:
php flarum fof:upload:backfill-dimensions --dry-run
Run the backfill with the default chunk size:
php flarum fof:upload:backfill-dimensions
Run with a smaller chunk size on a large forum:
php flarum fof:upload:backfill-dimensions --chunk=25
Note: The command is safe to run multiple times — it only processes images where
image_widthis stillNULL. If a file is unreachable (e.g. deleted from storage) it is skipped with a warning and the command continues.
BackfillThumbnailsCommand
The php flarum fof:upload:backfill-thumbnails command generates thumbnails for existing JPEG, PNG, and GIF uploads that were created before the thumbnail feature was introduced (or while the feature was disabled).
The command downloads each image via the same storage backend used to upload it, scales it to the configured thumbnail width, encodes it as WebP (or the original format if WebP is disabled), and writes the thumbnail file alongside the original. Uploads already having a thumbnail_url are skipped automatically.
Note: Imgur and private-shared uploads are skipped — thumbnails are not supported for those backends.
Options
| Option | Description |
|---|---|
--chunk=N |
Process files in batches of N (default: 50). Reduce this on very large forums. |
--dry-run |
Print how many images would be processed without making any changes. |
Examples
Preview how many images need thumbnails:
php flarum fof:upload:backfill-thumbnails --dry-run
Run with the default chunk size:
php flarum fof:upload:backfill-thumbnails
Run with a smaller chunk size on a large forum:
php flarum fof:upload:backfill-thumbnails --chunk=20
Note: The command is safe to run multiple times. If a file is unreachable it is skipped with a warning and processing continues.
Testing and Security Measures
FoF Upload includes automated tests to ensure:
✅ Valid files upload successfully ✅ Restricted files are blocked ✅ SVG sanitization removes potential XSS risks
🔍 Security Tests for Malicious Files
We specifically test against:
- HTML Injection (
.htmldisguised as an image) - MIME Spoofing (e.g.,
.pngcontaining a script) - Polygot Files (Files that act as two different formats)
- SVG Sanitization (
<script>,<foreignObject>, event handlers, external styles, etc) - ZIP & APK Handling (Ensuring APKs are valid and ZIPs are not misclassified)
Submitting Additional Test Cases
We welcome community contributes in all our extensions! Especially where security is concerned. If you find a new edge case or a file format that bypasses validation, please:
- Open an issue on GitHub
- Submit a test case as a PR under
tests/ - Describe the expected behaviour (Should the file be accepted? Should it be sanitized?)
🚀 These tests ensure FoF Upload remains secure and reliable for all Flarum users! 🚀
FAQ
- AWS S3: read the AWS S3 configuration page.
- Adding Templates: read the Custom Templates wiki page.
- Upgrading from flagrow/upload: read the wiki article.
Links
An extension by FriendsOfFlarum