Uploading images or files in Laravel

Video thumbnail

We've mastered Artisan, Laravel's command line, the next step is uploading files in Laravel; which is one of the most common tasks in any web application: images, documents, PDFs, etc. Laravel makes it especially easy thanks to its disk storage system, which can be local or in the cloud.

In this guide, I'll explain how to do it step by step, with real examples and recommendations based on my own experience working with Laravel.

⚙️ Prerequisites and Initial Configuration

Before starting, make sure you have a functional Laravel project and a basic form. If you don't have one yet, you can create your environment with:

$ laravel new upload-demo
$ cd upload-demo
$ php artisan serve

Laravel uses a "disk" system configured in the config/filesystems.php file.

By default, you will find three:

  • local: saves files to storage/app
  • public: accessible from the browser (linked to public/storage)
  • s3: for uploads to Amazon Web Services

To make the public disk accessible, run:

$ php artisan storage:link

This creates a symbolic link between storage/app/public and public/storage, allowing uploaded files to be displayed from the browser; this is ONLY if you want to upload the files to the public folder, otherwise or if you're not sure, you don't need to run the command above.

Creating the file upload form

The important thing to note here is the multipart form data field type to enable file uploads, and the file type field.

To process the form, we have the following:

<form action="{{ route('post.update', $post->id) }}" method="POST" enctype="multipart/form-data">
    @method('PATCH')
    @csrf

    <label for="">Title</label>
    <input type="text" name="title" value="{{ old('title', $post->title) }}">

    <label for="">Imagen</label>
    <input type="file" name="image">

    <button type="submit" class="btn btn-success mt-3">Send</button>
</form>

Tip: if you plan to upload multiple files, use multiple on the input and process an array of files in the controller.

Validation and Processing in the Controller

In this case, we're going to work with image uploads to a post, which has the following structure, its model:

class Post extends Model
{
    protected $fillable = ['title', 'slug', 'content', 'category_id', 'description', 'posted', 'image'];

    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

And the function to process the form:

public function update(PutRequest $request, Post $post)
    {
       // Resto de la funcion de carga 
        $data = $request->validated();
        if( isset($data["image"])){
            $data["image"] = $filename = time().".".$data["image"]->extension();
            $request->image->move(public_path("image/otro"), $filename);
        }
        $post->update($data);
}

It's very simple; remember that file uploading in Laravel is disk-based, a disk is anything that can store files, such as a local folder in the project in this case, but it can be another service like Amazon Web Services; you can confirm this in the config/filesystem.php file.

As for the rest, since the image is an optional upload, we check if it is defined; we generate the name of the image with the time() function and save it to a "disk," which is our local folder.

And that's it, with this, we can upload our files in Laravel.

Remember that this post is part of my Laravel course and book.

Saving Files to the Local Disk or Storage

Laravel abstracts file handling through the Storage facade. This allows you to change location (local, S3, FTP) without touching the base code.

Example with Storage:

use Illuminate\Support\Facades\Storage; 
Storage::put('archivos/demo.txt', 'Example content');

To get the public URL:

$url = Storage::url('archivos/demo.txt');

You can define custom disks in config/filesystems.php, for example:

'uploads' => [ 'driver' => 'local', 'root' => public_path('uploads'), ],

️ Displaying and Accessing Uploaded Files

If you saved the images to the public disk, you can display them directly:

<img src="{{ asset('storage/'.$post->image) }}" alt="Post image">

Or if you use the move() method to public_path(), adjust the path:

<img src="{{ asset('image/otro/'.$post->image) }}" alt="Post image">

This second option is more direct when working with local folders, but using Storage is more flexible if you plan to scale or use cloud services.

⚠️ Common Errors and Best Practices

Common Error    Recommended Solution
Not including enctype="multipart/form-data"    Make sure to add it to the form.
Permission denied when saving files    Check permissions for the /storage folder and run php artisan storage:link.
File overwriting    Use unique names (e.g., time() or UUID).
Files too large    Configure upload_max_filesize and post_max_size in php.ini.

Other best practices:

  • Always validate file type and size.
  • Use Storage instead of absolute paths for greater portability.
  • Avoid storing files directly in the public root if you plan to handle sensitive data.

Conclusion

Uploading files in Laravel is simple yet powerful. With just a form, validation, and a saving method, you can upload images or documents to your application.
In my case, the strategy of generating unique names and using dedicated folders has saved me a lot of headaches.

And if you're looking for greater flexibility, Storage allows you to migrate your local uploads to the cloud without modifying your application's code.

❓Frequently Asked Questions

Where are files saved in Laravel?
By default, in storage/app, or in storage/app/public if you use the public disk.

How do I validate that the file is an image?
Use the image or mimes:jpg,png,jpeg,gif rule in the validation.

How can I upload files to Amazon S3?
Configure the s3 disk in .env with your credentials and use Storage::disk('s3')->put().

Now, you'll want to learn how to download files in Laravel, which can be crucial in your project, depending on the topic.

We are going to learn how to load images or any other file in Laravel; from handling the form, going through the validations and reaching the controller.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español