Загрузка файлов в Laravel: частые ошибки новичков
Как в Laravel 11 принять файл от пользователя, сохранить его в нужную папку и отдать по ссылке — без потери файлов при валидации и без «файл не найден» на проде.

За полчаса вы сделаете форму загрузки картинки, которая сохранит файл в storage, проверит его тип и размер и вернёт публичную ссылку. Разберём это на Laravel 11.x (те же правила работают и в 10.x). От вас нужно: установленный Laravel-проект, PHP 8.2+, работающий php artisan serve и доступ к правке маршрутов и контроллеров.
Что нужно проверить до начала
Перед первой загрузкой убедитесь, что окружение готово. Три вещи ломают загрузку чаще всего именно на старте:
- Symlink на storage. Файлы из
storage/app/publicне видны браузеру, пока вы не создали символическую ссылку вpublic/storage. Команда —php artisan storage:link. - Лимиты PHP. В
php.iniсмотритеupload_max_filesizeиpost_max_size. Если файл больше — Laravel даже не увидит его, форма молча вернёт пустой запрос. - Права на запись. Веб-сервер должен иметь доступ на запись в каталог
storage. На локальной машине черезartisan serveпроблем обычно нет, на проде под nginx+php-fpm — бывают.
Шаг 1. Форма с правильным атрибутом
Самая частая ошибка новичка живёт прямо в HTML: без enctype="multipart/form-data" файл не уходит на сервер вообще. Форма отправится, но $request->file() вернёт null.
<!-- resources/views/upload.blade.php -->
<form action="/upload" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="avatar">
<button type="submit">Загрузить</button>
</form>
Проверка: откройте страницу, инструменты разработчика, вкладку Network. При отправке в заголовках запроса должен быть Content-Type: multipart/form-data.
Шаг 2. Маршрут
Добавьте два маршрута в routes/web.php — один показывает форму, второй принимает файл.
use App\Http\Controllers\UploadController;
Route::get('/upload', [UploadController::class, 'form']);
Route::post('/upload', [UploadController::class, 'store']);
Проверка: php artisan route:list покажет обе строки с методами GET и POST.
Шаг 3. Контроллер с валидацией и сохранением
Создайте контроллер: php artisan make:controller UploadController. Валидацию делаем до сохранения — иначе на диск попадёт что угодно.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UploadController extends Controller
{
public function form()
{
return view('upload');
}
public function store(Request $request)
{
// mimes проверяет реальный тип файла, а не расширение
$request->validate([
'avatar' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048', // max в килобайтах
]);
// store вернёт путь вида avatars/xxxxx.jpg с уникальным именем
$path = $request->file('avatar')->store('avatars', 'public');
// публичная ссылка на файл
$url = asset('storage/' . $path);
return back()->with('uploaded', $url);
}
}
Проверка: загрузите картинку до 2 МБ — она появится в storage/app/public/avatars, а ссылка $url откроется в браузере. Загрузите PDF — вернётся ошибка валидации, файл на диск не попадёт.
Почему именно так
Метод store() сам генерирует уникальное имя файла — это спасает от перезаписи, когда два пользователя грузят файлы с одинаковым названием. Второй аргумент 'public' — это имя диска из config/filesystems.php, а не путь. Диск public по умолчанию пишет в storage/app/public, которое через symlink видно как public/storage.
Расширение файла ничего не гарантирует. Пользователь переименуетvirus.exeвphoto.jpgза секунду. Правилоmimesв Laravel проверяет MIME-тип по содержимому через guessed-тип файла, а не по строке после точки — поэтому оно и нужно.
Если не получилось
- «The avatar field is required» при выбранном файле. Проверьте атрибут
enctypeв форме и совпадениеname="avatar"с ключом в правилах валидации. Расхождение имени — вторая по частоте причина. - Файл сохранился, но ссылка отдаёт 404. Не выполнена
php artisan storage:linkлибо symlink указывает не туда. Удалитеpublic/storageи создайте ссылку заново. - «The avatar failed to upload» или пустой запрос при большом файле. Файл превысил
upload_max_filesizeилиpost_max_sizeвphp.ini. Правилоmax:2048тут не помогает — PHP отбрасывает файл раньше Laravel. - «Disk [public] does not have a configured driver». Кто-то удалил или переименовал диск в
config/filesystems.php. Верните секциюpublicв исходный вид или укажите существующий диск. - Permission denied при сохранении. У процесса PHP нет прав на запись в
storage. На проде выдайте владельца каталога пользователю веб-сервера.
Ещё две ловушки, о которых узнают поздно
Первая — хранение пути к файлу в базе. Сохраняйте относительный путь (avatars/xxx.jpg), а не полный URL. Домен и структуру дисков вы ещё поменяете, и жёстко записанный https://... в базе превратит миграцию на новый хостинг в разбор тысяч строк.
Вторая — диск public для приватных файлов. Всё, что попало в storage/app/public, доступно по прямой ссылке любому, кто её угадает. Паспорта, договоры и чеки туда класть нельзя — для них используйте диск без публичного symlink и отдавайте файлы через контроллер с проверкой прав.
Соответствие настроек: что куда вписать
| Что настраиваете | Где | Значение / пример |
|---|---|---|
| Максимальный размер файла | php.ini | upload_max_filesize, post_max_size |
| Лимит в приложении | правило валидации | max:2048 (в КБ) |
| Куда пишутся файлы | config/filesystems.php | диск public → storage/app/public |
| Публичный доступ к файлам | artisan storage:link | public/storage → storage/app/public |
| Атрибут формы | Blade-шаблон | enctype="multipart/form-data" |
Что делать дальше
Когда базовая загрузка работает, настройте на проде значения upload_max_filesize и post_max_size под реальные размеры файлов и синхронизируйте их с правилом max. Дальше стоит посмотреть на диск s3 в config/filesystems.php — переезд с локального диска в облако меняется одной строкой конфига, если вы с самого начала не хардкодили пути. Инвалидация старых файлов при обновлении аватара — отдельная тема, здесь мы её не трогаем. Точные имена методов и опции валидации проверяйте в документации Laravel вашей версии.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: Laravel — File Storage, Laravel — Validation (Available Rules)
Частые вопросы
Что делать, если у меня Laravel 10, а не 11?
Почему $request->file('avatar') возвращает null, хотя файл я выбрал?
Ссылка на файл открывается локально, а на проде отдаёт 404. Почему?
Как ограничить размер загружаемого файла?
Можно ли доверять расширению файла для проверки типа?
Безопасно ли хранить приватные документы на диске public?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.