Magento 2: Upload File & Image 2026

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



Before You Spend More on Ads — Make Sure Your Store Won’t Fail

One expert review. Real insights. No obligation.

Copyright © 2026 ArmMage LLC. All rights reserved.