This guide demonstrates the modern and recommended way to upload files and images in Magento 2.4.x using UploaderFactory, proper dependency injection, and a service-based architecture.
Instead of placing upload logic directly inside controllers, Magento 2 best practices recommend moving upload functionality into a dedicated model or service class.
Why Update the Old Method?
Older tutorials commonly used:
\Magento\MediaStorage\Model\File\Uploader
directly inside controllers.
While it still works, newer Magento 2 development standards prefer:
- clean architecture
- service classes
- dependency injection
- reusable upload logic
- better validation and maintainability
This approach is more suitable for Magento 2.4.6 / 2.4.7 / PHP 8.2+ projects.
HTML Form
Make sure your form contains:
enctype="multipart/form-data"
Otherwise file uploads will not work.
Example:
<form action="/your-upload-url" method="post" enctype="multipart/form-data">
<label for="my_custom_file">
Upload File
</label>
<input
type="file"
id="my_custom_file"
name="my_custom_file"
accept=".jpg,.jpeg,.png,.webp,.pdf,.doc,.docx,.zip"
/>
<button type="submit">
Upload
</button>
</form>
Create Upload Service Class
File:
app/code/ArmMage/FileUpload/Model/FileUploader.php
Code:
<?php
declare(strict_types=1);
namespace ArmMage\FileUpload\Model;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\MediaStorage\Model\File\UploaderFactory;
class FileUploader
{
private const UPLOAD_DIR = 'armmage/uploads';
public function __construct(
private readonly Filesystem $filesystem,
private readonly UploaderFactory $uploaderFactory
) {
}
/**
* Upload file
*/
public function upload(string $fileId): ?string
{
$mediaDirectory = $this->filesystem->getDirectoryWrite(
DirectoryList::MEDIA
);
$target = $mediaDirectory->getAbsolutePath(
self::UPLOAD_DIR
);
try {
$uploader = $this->uploaderFactory->create([
'fileId' => $fileId
]);
$uploader->setAllowedExtensions([
'jpg',
'jpeg',
'png',
'webp',
'pdf',
'doc',
'docx',
'zip'
]);
$uploader->setAllowRenameFiles(true);
$uploader->setAllowCreateFolders(true);
$result = $uploader->save($target);
if (!$result || empty($result['file'])) {
return null;
}
return self::UPLOAD_DIR . $result['file'];
} catch (\Exception $e) {
throw new LocalizedException(
__('File upload failed: %1', $e->getMessage())
);
}
}
}
Controller Example
<?php
declare(strict_types=1);
namespace ArmMage\FileUpload\Controller\Index;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Controller\ResultFactory;
use ArmMage\FileUpload\Model\FileUploader;
class Upload implements HttpPostActionInterface
{
public function __construct(
private readonly Context $context,
private readonly FileUploader $fileUploader
) {
}
public function execute(): Redirect
{
try {
$filePath = $this->fileUploader->upload(
'my_custom_file'
);
if ($filePath) {
$this->context->getMessageManager()->addSuccessMessage(
__('File uploaded successfully.')
);
}
} catch (\Exception $e) {
$this->context->getMessageManager()->addErrorMessage(
__($e->getMessage())
);
}
return $this->context
->getResultFactory()
->create(ResultFactory::TYPE_REDIRECT)
->setPath('*/*/');
}
}
Uploaded File Location
Files will be stored in:
pub/media/armmage/uploads/
Security Recommendations
Always:
- validate allowed extensions
- validate MIME types
- avoid executable uploads
- never allow
.php - limit file size
- sanitize filenames
For image-only uploads:
$uploader->setAllowedExtensions([
'jpg',
'jpeg',
'png',
'webp'
]);
Optional Improvements
You can additionally implement:
- MIME validation
- image dimension validation
- max file size validation
- admin configuration for allowed extensions
- automatic WebP conversion
- AWS S3 storage
- database file metadata storage
Result
This modern approach is:
- Magento 2.4.x compatible
- PHP 8.2+ compatible
- reusable
- SOLID-friendly
- cleaner for enterprise projects
- safer than old controller-based upload examples
Comments