Lesson 30 of 50 - Learn how to upload, validate, store, display, and delete files in Laravel.
File uploading allows users to send files from their computer to a Laravel application. Examples include profile photos, student documents, assignment files, and PDF certificates.
Laravel provides tools for:
Important:
An HTML upload form must use
enctype="multipart/form-data".
Without it, the browser will not submit uploaded files
correctly.
<form action="/upload" method="POST"
enctype="multipart/form-data">
<input type="file" name="document">
<button type="submit">Upload File</button>
</form>
Create a controller using Artisan:
php artisan make:controller FileUploadController
Open routes/web.php and add:
use App\Http\Controllers\FileUploadController;
use Illuminate\Support\Facades\Route;
Route::get('/upload', [FileUploadController::class, 'create'])
->name('files.create');
Route::post('/upload', [FileUploadController::class, 'store'])
->name('files.store');
The GET route displays the upload form. The POST route receives and processes the uploaded file.
These routes normally use Laravel's web middleware, which provides CSRF protection for the POST request.
Create the file
resources/views/upload.blade.php.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload File</title>
</head>
<body>
<h2>Upload a File</h2>
@if (session('success'))
<p style="color: green;">
{{ session('success') }}
</p>
@endif
@if ($errors->any())
<ul style="color: red;">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
<form action="{{ route('files.store') }}"
method="POST"
enctype="multipart/form-data">
@csrf
<label for="document">Choose a file:</label>
<input type="file" name="document" id="document" required>
<button type="submit">Upload</button>
</form>
</body>
</html>
The @csrf directive adds a CSRF token
to the form. The multipart/form-data
encoding allows the browser to submit the file.
Open
app/Http/Controllers/FileUploadController.php.
Add the following code:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FileUploadController extends Controller
{
public function create()
{
return view('upload');
}
public function store(Request $request)
{
$request->validate([
'document' => [
'required',
'file',
'mimes:pdf,jpg,jpeg,png,doc,docx',
'max:2048',
],
]);
$path = $request->file('document')
->store('documents', 'public');
return redirect()
->route('files.create')
->with('success', 'File uploaded successfully!');
}
}
Explanation:
required means a file must be selected.file verifies that the uploaded input is a file.mimes restricts allowed file types.max:2048 limits the file to 2048 KB, or 2 MB.store() saves the file and returns its storage path.
The file is stored on Laravel's public disk inside
storage/app/public/documents when using
the standard local public-disk configuration.
The $path variable contains the stored
relative path. Save it in a database if you need to
retrieve the file later.
To make files on Laravel's public disk accessible through the web server, run:
php artisan storage:link
This normally creates a symbolic link from
public/storage to
storage/app/public.
For example, a file stored at
documents/example.pdf can be accessed
through the public storage URL:
/storage/documents/example.pdf
Generate the URL in Laravel:
use Illuminate\Support\Facades\Storage;
$url = Storage::disk('public')
->url('documents/example.pdf');
You can display a public image in Blade:
<img
src="{{ Storage::disk('public')->url($path) }}"
alt="Uploaded image"
width="200"
>
Make sure Storage is imported in PHP
code where required. Files on a public disk should
only contain content intended for public access.
In real applications, you usually save the file path in a database so the uploaded file can be associated with a student, user, or document record.
For this example, assume a documents table
has title and file_path
columns.
Example migration:
Schema::create('documents', function ($table) {
$table->id();
$table->string('title');
$table->string('file_path');
$table->timestamps();
});
Add this import when using the migration:
use Illuminate\Support\Facades\Schema;
Create a Document model:
php artisan make:model Document
In app/Models/Document.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Document extends Model
{
protected $fillable = [
'title',
'file_path',
];
}
Save the uploaded file and its path:
use App\Models\Document;
use Illuminate\Http\Request;
public function store(Request $request)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'document' => [
'required',
'file',
'mimes:pdf,jpg,jpeg,png,doc,docx',
'max:2048',
],
]);
$path = $request->file('document')
->store('documents', 'public');
Document::create([
'title' => $validated['title'],
'file_path' => $path,
]);
return redirect()
->route('files.create')
->with('success', 'Document saved successfully!');
}
Add a title input with name="title" to
your form for this version of the controller.
Run the migration before using this example.
You can retrieve the saved document records from the database and display links to their files.
Controller example:
use App\Models\Document;
public function index()
{
$documents = Document::latest()->paginate(10);
return view('documents.index', compact('documents'));
}
Blade example:
<h2>Uploaded Documents</h2>
@forelse ($documents as $document)
<div>
<h4>{{ $document->title }}</h4>
<a
href="{{ Storage::disk('public')->url($document->file_path) }}"
target="_blank"
rel="noopener"
>
View File
</a>
</div>
@empty
<p>No documents uploaded yet.</p>
@endforelse
{{ $documents->links() }}
Import the Storage facade in the Blade file if you use it there:
@php
use Illuminate\Support\Facades\Storage;
@endphp
Alternatively, generate file URLs in the controller and pass them to the view. Only generate public URLs for files intended to be publicly accessible.
Laravel's Storage facade provides a method for deleting stored files.
Delete a file from the public disk:
use Illuminate\Support\Facades\Storage;
Storage::disk('public')->delete(
'documents/example.pdf'
);
Delete a document and its stored file:
use App\Models\Document;
use Illuminate\Support\Facades\Storage;
public function destroy(Document $document)
{
Storage::disk('public')->delete($document->file_path);
$document->delete();
return redirect()
->back()
->with('success', 'Document deleted successfully!');
}
Define a route for deletion:
Route::delete(
'/documents/{document}',
[DocumentController::class, 'destroy']
)->name('documents.destroy');
The controller method assumes route-model binding is
configured by the parameter name and that the
Document model exists.
Security reminders:
Create a document upload module for a student management system.
Challenge:
Build a student document upload page that accepts PDF certificates and JPG or PNG profile photos. Store the student ID with each document, validate the upload, and ensure students can access only documents they are authorized to view.
Remember to run php artisan storage:link
when using Laravel's public local disk, and configure
private storage for sensitive student documents.
1. Which form encoding is required for file uploads?
2. Which Laravel method can store an uploaded file?
3. Which command creates the public storage symbolic link?