How to do an image upload form and handler in Laravel 9 with file validation.
Included is a controller, upload form and migration for the database table to store information about the image upload.
The controller
Create the image upload controller with
php artisan make:controller ImageUploadController
This will handle showing the upload form blade file and the processing of the image upload (validation, renaming and storing).
Inside app/Http/Controllers/ImageUploadController.php paste the following:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; class ImageUploadController extends Controller { public function index() { return view('images.upload'); } public function store(Request $request) { $request->validate([ 'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048' ]); $original_name = $request->image->getClientOriginalName();//Original image file name $file_size = $request->image->getSize();//Size in bytes $extension = $request->image->extension();//Image file extension eg. jpg or png $directory = 'uploads/' . date('Y') . '/' . date('m');//Upload and move image into this directory $id = Str::random(8);//Random 8 character string $image_name = $id . '.' . $extension; $full_save_path_name = $directory . '/' . $image_name; $request->image->move(public_path($directory), $image_name);//Save into: public/images DB::table('images')->insert([ 'id' => $id, 'user_id' => Auth::id(), 'size' => $file_size, 'original_name' => $original_name, 'directory' => $directory, 'extension' => $extension, 'uploaded' => date('Y-m-d H:i:s') ]); return back() ->with("success", "You have successfully uploaded $original_name saved as: $image_name") ->with("image", $full_save_path_name); } }
The uploaded image will be validated with 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048'
this means the form field is required, it must be an image in jpg/png/gif/svg format and the maximum allowed size is 2048 kilobytes.
The save directory will be public/uploads/YEAR/MONTH e.g public/uploads/2022/04
The image will be renamed as a randomly generated id, 8 characters long.
The routes
In routes/web.php add:
Route::controller(ImageUploadController::class)->middleware('auth')->group(function(){ Route::get('image-upload', 'index'); Route::post('image-upload', 'store')->name('image.store'); });
These two routes are protected by ‘auth’ meaning only logged in users can view them. The two routes are /image-upload
as a GET which calls index and /image-upload
as a POST which calls the store method from ImageUploadController.
Run php artisan route:cache
to refresh the routes.
The upload form page
This will be the form upload page as a blade file, put in resources/views/images
the file upload.blade.php
with the following:
<!DOCTYPE html> <html> <head> <title>Upload image</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://write.corbpie.com/wp-content/litespeed/localres/aHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS8=ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <div class="card"> <div class="card-body"> <div class="panel panel-primary"> <div class="panel-heading"> <h2>Upload image</h2> </div> <div class="panel-body"> @if ($message = Session::get('success')) <div class="alert alert-success alert-block"> <strong>{{ $message }}</strong> </div> <img class="img-fluid" src="{{ Session::get('image') }}" alt="uploaded image"> @endif <form action="{{ route('image.store') }}" method="POST" enctype="multipart/form-data"> @csrf <div class="mb-3"> <label class="form-label" for="image_input">Image:</label> <input type="file" name="image" id="image_input" class="form-control @error('image') is-invalid @enderror"> @error('image') <span class="text-danger">{{ $message }}</span> @enderror </div> <div class="mb-3"> <button type="submit" class="btn btn-success">Upload</button> </div> </form> </div> </div> </div> </div> </div> </body> </html>
Normally this blade file will be an extension of the main layout however for this example it encompasses the whole page with the header and CSS source.
The migration
This is if you want to insert details about the image upload into the database.
If you do not want to do this skip this section and delete the DB::table('images')->insert...
section of the ImageUploadController store method.
Run: php artisan make:migration images
Find the database/migrations create_images_table.php file and paste:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::create('images', function (Blueprint $table) { $table->char('id', 8); $table->bigInteger('user_id'); $table->integer('size'); $table->string('original_name'); $table->string('directory'); $table->string('extension'); $table->date('uploaded'); $table->primary(['id']); }); } public function down() { Schema::dropIfExists('images'); } };
Run migrate to create this table: php artisan migrate
Usage
Once logged in navigate to /image-upload
If you upload a non-image you get this notice
or if the image is oversized (greater than 2048 Kilobytes)
Upon a successful upload: