Notice: this article is from 2017, we have newer article on this topic!
Multiple File Upload with Dropzone.js and Laravel MediaLibrary Package
File upload is one of the most commonly used features in web-projects. And it seems pretty easy – form, submit, validation, store. But it gets a little more complex if you want to allow your users to upload more than one file with one input – let’s see how it’s done in Laravel.
1. Preparing the database
Let’s have a simple and probably the most often example – product and its photos. To simplify let’s take only necessary field – product will have a title and N photos. So we create models with migrations – with these commands:
php artisan make:model Product -m php artisan make:model ProductsPhoto -m
Notice: this -m parameter means that migration should be automatically created with the model – more about it here.
File app/Product.php:
namespace App; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $fillable = ['name']; }
Migration of products table:
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateProductsTable extends Migration { public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); } public function down() { Schema::drop('products'); } }
Now, a little more complicated thing – for products_photos table we have a model app/ProductsPhoto.php with a relationship method:
namespace App; use Illuminate\Database\Eloquent\Model; class ProductsPhoto extends Model { protected $fillable = ['product_id', 'filename']; public function product() { return $this->belongsTo('App\Product'); } }
And, finally, migration for products_photos table:
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateProductsPhotosTable extends Migration { public function up() { Schema::create('products_photos', function (Blueprint $table) { $table->increments('id'); $table->integer('product_id')->unsigned(); $table->foreign('product_id')->references('id')->on('products'); $table->string('filename'); $table->timestamps(); }); } public function down() { Schema::drop('products_photos'); } }
Go to QuickAdminPanel.com
2. Structure: Routes, Controllers and Views
We start with a default Laravel project and will create two pages – upload form and result page. Basically, we need two routes and one controller.
File routes/web.php (you can choose different URLs or method names):
Route::get('/upload', 'UploadController@uploadForm'); Route::post('/upload', 'UploadController@uploadSubmit');
Next we generate a controller – there’s an artisan command for that:
php artisan make:controller UploadController
And for now we fill the methods with something like this:
namespace App\Http\Controllers; use Illuminate\Http\Request; class UploadController extends Controller { public function uploadForm() { return view('upload_form'); } public function uploadSubmit(Request $request) { // Coming soon... } }
Let’s load URL /upload in the browser.

Got this error “View [upload_form] not found.”? GOOD. It means two things:
- Route and Controller work ok;
- It’s time to create the missing view
3. View with Upload Form
For our form let’s use default HTML tags without any Form packages – our form will look like this:

<form action="/upload" enctype="multipart/form-data" method="post">
{{ csrf_field() }}
Product name: <br>
<input name="name" type="text">
<br><br>
Product photos (can attach more than one): <br>
<input multiple="multiple" name="photos[]" type="file">
<br><br>
<input type="submit" value="Upload">
</form>
4. Validation Request
Now let’s get back to our Controller and start filling in uploadSubmit() method. Before actually uploading the file, we probably want to validate the form data.
Let’s say that we have a required product name and files are images not bigger than 2 MB. We create a request file for it:
php artisan make:request UploadRequest
And then open the file app/Http/Requests/UploadRequest.php.
class UploadRequest extends FormRequest { public function authorize() { return false; } public function rules() { return [ // ]; } }
First, we change authorize() method to return true, otherwise we would get “unauthorized action” on submit.
Now, what we’re interested in is rules() method. We need to fill that array with our own rules of validation.
First, we add product name as required, so we have:
return [ 'name' => 'required' ];
Now, we need to fill the rules for photos. If we had only one field photo, it would look like this:
return [ 'name' => 'required', 'photo' => 'image|mimes:jpeg,bmp,png|size:2000' ];
Notice: you can see all the validation rules (and add more) on this page of official documentation.
But we have more than one file, right? So we can fill in the rules() array dynamically! With a loop, something like this:
public function rules() { $rules = [ 'name' => 'required' ]; $photos = count($this->input('photos')); foreach(range(0, $photos) as $index) { $rules['photos.' . $index] = 'image|mimes:jpeg,bmp,png|max:2000'; } return $rules; }
Next step – we need to apply this Request class to the Controller, so we change the parameter of the method. Also don’t forget to include it in use on top – PhpStorm did it automatically for me.
namespace App\Http\Controllers; use App\Http\Requests\UploadRequest; class UploadController extends Controller { public function uploadForm() { return view('upload_form'); } public function uploadSubmit(UploadRequest $request) { // Coming soon... } }
Finally, let’s show the validation errors, if there are any. Almost copy-paste from the original Laravel documentation:
@if (count($errors) > 0)
<ul><li>{{ $error }}</li></ul>
@endif
<form action="/upload" enctype="multipart/form-data" method="post">...</form>
Now, if I don’t enter product name and upload an image which is too big – here’s what I will see:

5. Storing Data and Files
After all this hard work – let’s finally upload the data. We have two things to take care of – database and file storage. Let’s start with the files, and here we need to know about filesystems in Laravel.
There is a file config/filesystems.php where you specify the locations for your file storage. It allows you to easily configure external storage like Amazon S3, but we won’t do it in this tutorial – we will stick with default parameters:
return [ 'default' => 'local', 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ], // ...
Basically, it means that all your files will be stored in /storage/app folder. Not in /public, so safely and not accessible directly from browser URL. But you can change it here, if needed.
The file upload itself is incredibly simple in Laravel. Our whole Controller method will look like this:
public function uploadSubmit(UploadRequest $request) { $product = Product::create($request->all()); foreach ($request->photos as $photo) { $filename = $photo->store('photos'); ProductsPhoto::create([ 'product_id' => $product->id, 'filename' => $filename ]); } return 'Upload successful!'; }
As you can see, the only method for uploading file is $photo->store(‘photos’). Parameter means what sub-folder to use for storage, so in this case it will be /storage/app/photos. Filename will be created dynamically to a random string. Like this:

And here’s how the result looks in database:


Basically, that’s it! Of course, you can go a step further and add more validation rules, image resize, thumbnails and more functions. But that’s a topic for another article, I can recommend a few packages to work with images:
Finally, if you want to play around with the code listed here, I’ve prepared you the archive to download. Enjoy!
Question: instead of writing validation by counting inputs and making foreach loop, isn’t better to use validation of array based input? See https://laravel.com/docs/5.4/validation#validating-arrays
Good question, Peter. For some reason it didn’t work for me when I tried photos.* => XXX. Probably with files it works differently. Maybe you would write the code and I would update the article? Thanks a lot!
Array validation wouldn’t work with just “photos.*”, but you could use “photos.*.photo” instead, and rename the file input to “photos[][photo]”
Thanks a lot Michael! I guess I will leave article the same, and people who would read comments will see both ways.
This is also good way to validate various number of photos in array , that you get back from input request. It worked for me. Cheers.
$validate = count($photo);
$photos = $input[‘photos’];
$this->validate(request(),[
‘photos’ => ‘required|min:’.$validate
]);
У меня такая Ошибка
Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)
i have error!
Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)
The correct way would be (from laravel doc):
$rules = [
“photo” => “required|array”,
“photo.*” => “required|image|mimes:jpeg,bmp,png|max:2000”
…
]
This worked perfectly for me without the need to do the loop on the photos array.
Thanks Marchrius! This approch worked prefectly for me.
Thanks for the detailed write up! Just one minor thing I’d argue, I feel that the naming of the second model and table would make a lot more sense as “ProductPhoto” and “product_photos” respectively, instead of using an awkward convention. It really makes no sense to use plural forms for models, as your model basically describes an object (singular), any relation to another model which might be plural is described by a property/method of the model, not the name itself. And in this case, only your product model is “really” aware that there is a 1 to n relation, so it makes even less sense, on top of that, if you’re going plural, it should’ve been ProductPhotos, because you have more photos per product, but again, that should not be conveyed through the name.
For your own sanity while coding, using names that are semantically and grammatically correct will lead to less confusion and code that reads naturally.
Thank you, Ken, for the detailed opinion. In general, I agree, that is my personal preference to have database tables “***S” and ***S_***S” therefore I’ve used models like this.
Don’t have time to change the article now, including screenshots and code files, but will use this logic for the future articles.
Better validate is use ‘photos.*.required’ etc. For what you did a loop for this?
Please try the code as you written “photos.*.required” and tell me if it works with file upload fields. And see other comments above.
from the archive
I modified web.php
Route::get(‘/’, function () {
return view(‘welcome’);
});
to have the upload form
I submited the form but error:
TokenMismatchException in VerifyCsrfToken.php line 68:
How can I correct this ?
You probably don’t have this line in your form:
{{ csrf_field() }}
Or any other CSRF token.
Thank you for the code and help here. But I was in need of code to access the stored pictures for that particular product.
Say i have 10 products and each product have 5 or more images then i need to show the images when that particular product is clicked.That is at home page we have only the list of product with thumbnails and when that thumbnails is clicked the details of that product should come with the related images, Can you help me with that? Please… 🙂
Hi Tek Raj,
How exactly can I help you? What code have you tried and it didn’t work? Or you want me to write a full tutorial for this? I think it’s pretty easy to find some related tutorials on this topic.
I tried a lot. I couldn’t find it even in stack overflow. Yours is the first tutorial i found for storing image/files using relationship. Up to uploading, I completed and worked fine I just needed is the accessing part. You could help me either by providing me the technique following your uploading tutorial or you could write a sample code. Either way its fine for me. I would be very grateful to you. I am stuck with this problem since 3 days. And finally i found yours tutorial so 🙂
This is my Route:
Route::get(‘/upload’, ‘UploadController@uploadForm’);
Route::post(‘/upload’, ‘UploadController@uploadSubmit’);
Route::get(‘/upload/{id}’,’UploadController@show’);
This is my show function inside UploadController:
public function show($id)
{
// $data=Product::find($id);
$data =ProductsPhoto::with(‘Product’)->find($id);
return view(‘showpage’,compact(‘data’));
}
This is my showpage code:
{{$data->name}}
name}}”/>
{{– images}}” alt=”a” title=”a” id=”wows1_0″/>
images}}” alt=”a” title=”a” id=”wows1_0″/> –}}
This is showpage :
{{$data->name}}
name}}”/>
{{– images}}” alt=”a” title=”a” id=”wows1_0″/>
images}}” alt=”a” title=”a” id=”wows1_0″/> –}}
in controller in show function when i use “$data=Product::find($id);” I can access the product name but if “$data =ProductsPhoto::with(‘Product’)->find($id);” is used i cant get name nor image.I think my img src is wrong. what is the img src for the image stored in storage/app/photos in laravel.
This thread will help you – to get to /storage/app/photos folder you need to create a separate route. Which, by the way, you can protect by some middleware or other logic.
http://stackoverflow.com/questions/30191330/laravel-5-how-to-access-image-uploaded-in-storage-within-view
okay I will give it a shot. Thank you 🙂
please provide a tutorial for displaying all images related to particular product. We couldn’t do it.
Sorry, we don’t plan to write tutorial about this, have other topics on the list.
if you use just image mime type for validation in your code then this code can be exploited by uploading a crafted image with php extension
search in google for image mime type exploit
I was gonna write the same, I think this code is insecure as a user can upload a .php file crafted that appears to be an image, and because this code keeps the original extension
It seems the validator check the extension too… http://stackoverflow.com/questions/29842625/laravel-5-mime-validation
iam getting this error
“FileException in UploadedFile.php line 251:
The file “Lighthouse.jpg” was not uploaded due to an unknown error. “
This is not succeeding – have removed everything relating to the database, just trying to get the
“`->$photo->store()“` to work.
As part of my troubleshooting I;ve inserted a “`dump($filename);“` in the “`foreach ($request->photos as $photo) {““ part of the “`Uploadcontroller“`.
I suspect something critical isn’t happening in the store() function, but as puzzled on how to get to it.
A pointer in the right direction would be appreciated.
Cheers – Miles Thompson
Enfield NS, Canada
i select array = [Untitled.png, 1.png, CV_Bui-Huu-Tai.pdf, Map to TMA.jpg]. Why is it not error with CV_Bui-Huu-Tai.pdf?.
public function rules()
{
$nbr = count($this->input(‘file’));
foreach(range(0, $nbr) as $index) {
$rules[‘file.’. $index] = ‘image|max:4000’;
}
return $rules;
}
hi
this code is helped me to create images
but how to show images and delete.
plz, reply.
thanks.
Insanely Simple Tutorial POVILAS Sir ! , Was too Easy To Follow ! Thanks a Ton !!
Hi guys, I keep getting this error –
“message”: “SQLSTATE[HY000]: General error: 1364 Field ‘product_id’ doesn’t have a default value (SQL: insert into `products_photos` (`photo`, `updated_at`, `created_at`) values (photos/H0ZXbHGy5GYlap2DVeP4xj0GqYwoZEv0Uqfo9VqK.jpeg, 2017-09-12 09:41:23, 2017-09-12 09:41:23))”,
Here is my upload controller code –
public function store(UploadRequest $request)
{
$product = Product::create($request->all());
foreach ($request->images as $photo) {
$imageName = $photo->store(‘photos’);
ProductsPhoto::create([
‘product_id’ => $product->id,
‘photo’ => $imageName
]);
}
return view(‘products.item’, compact(‘product’));
}
What could probably be the problem? I would appreciate your answers.
I think you should write the full path of Product like this:
$product = \App\Models\Product::create($request->all());
Hey Jerry, you need to save the product first..then the id will work.
Wow! thanks much.
could you tell me how you could apply intervention image with this example?
Is this tutorial incomplete? I get the following: local.ERROR: Class ‘App\Http\Controllers\Product’ not found.
It seems to be stumbling on Product::create because there is no Controller?
Hi Eric
Product is not a controller, it’s a module, you need to add “use app\Product” on top of your controller.
Thanks! That’s what I needed – works great!
I used this on a test project… It works perfectly! Only issue is when I implemented Intervention, I started having issues with imagick and bmp validation. So, has anyone experience the same validation issue and how did you solve the issue?
I tried this to laravel 5.4 api but return null.. i used angularjs 4 to upload images
fileChange(event) {
let fileList: FileList = event.target.files;
if(fileList.length > 0) {
let file: File = fileList[0];
var formData= new FormData();
formData.append(‘uploadFile’, file, file.name);
console.log(formData);
console.log(file);
// let headers = new Headers();
/** No need to include Content-Type in Angular 4 */
// headers.append(‘Accept’, ‘application/json’);
let currentUser = JSON.parse(localStorage.getItem(‘currentUser’));
let headers = new Headers({ ‘Authorization’: ‘Bearer ‘ + currentUser });
// headers.append(‘Content-Type’, ‘multipart/form-data; charset=utf-8; boundary=’ + Math.random().toString().substr(2));
let options = new RequestOptions({ headers: headers });
this.http.post(this.apiUrl+’v1/createEmp’, formData, options)
.map(res => res.json())
// .catch(error => Observable.throw(error))
.subscribe(
data => console.log(data),
error => console.log(error)
);
}
}
public function createEmployee(UploadRequest $request)
{
$data=$request->all();
return response()->json($data,200);
}
I’m in the same situation with angular 4.4.3, y tried and the request has no value, I tried this and nothing.
Angular Code:
const formData = new FormData();
let photos = [];
photos = this.photosHandlerService.getPhotos();
for (let i = 0; i {
const confirmation = response;
if (confirmation) {
console.log(‘I have answer’);
}
});
And the backend with laravel 5.6:
public function(Request $request) {
if($request->hasFile(‘uploaded0’)) {
return response(‘true’, 200);
}
else {
return response(‘false’, 200);
}
}
I never recieve true
Great article man.! you saved my alot of time
i am getting this error
“SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘name’ cannot be null (SQL: insert into `products` (`name`, `updated_at`, `created_at`) values (, 2017-11-17 17:36:44, 2017-11-17 17:36:44))
when i press submit button without inserting data.
i think validation output is not properly handle. how can i fix this???
Hi.. This is good tutorial, but how to show the images ? Thanks
[…] following the following tutorial -> http://laraveldaily.com/upload-multiple-files-laravel-5-4. When I go to post my advert, the add goes thorugh without the photographs. My error message […]
I am very interesting your code about upload a lot up the image. And could you share for me to refer, please?
wondering why you used input instead of file in the request validation, that gave me errors for days
Hi, i get error says “count(): Parameter must be an array or an object that implements Countable” in UploadRequest.php
I saw this on Laravel 5.5 as well. Try this:
$allFiles = $this->allFiles();
$photos = count($allFiles[‘photos’]);
If You are so confused use the function
“`function uploadFiles($fileNames=”,$destinationPath=”)
{
foreach($fileNames as $fileName)
{
$fileOriginalName = $fileName->getClientOriginalName();
$fileName->move($destinationPath, $fileOriginalName);
$allFileNames[] = $fileOriginalName;
}
return json_encode($allFileNames);
}“`
I need to update multiple image in laravel 5.8.
Any one can help me to provide the source code??
Can anyone help me. I am new in laravel. I able to select multiple image but when I click Add button, nothing happens on my system. The images are not saved in my public file and database. Soemthing wrong with my controller