In a lot of our projects we need to make a multiple image uploader. In this tutorial, We will make an advanced AJAX uploader. This uploader will:

  1. upload multiple images via AJAX.
  2. show the thumbnails of those uploads using Dropzone.js
  3. show the URL link of the uploaded images and copy it to the clipboard using clipboardjs.

Let’s start !

The Controller:

First of all, let’s make a controller for our upload function:

php artisan make:controller UploadController

let’s write our upload function (each line explained)

public function upload(Request $request)
    // Creating a new time instance, we'll use it to name our file and declare the path
    $time = Carbon::now();
    // Requesting the file from the form
    $image = $request->file('file');
    // Getting the extension of the file 
    $extension = $image->getClientOriginalExtension();
    // Creating the directory, for example, if the date = 18/10/2017, the directory will be 2017/10/
    $directory = date_format($time, 'Y') . '/' . date_format($time, 'm');
    // Creating the file name: random string followed by the day, random number and the hour
    $filename = str_random(5).date_format($time,'d').rand(1,9).date_format($time,'h').".".$extension;
    // This is our upload main function, storing the image in the storage that named 'public'
    $upload_success = $image->storeAs($directory, $filename, 'public');
    // If the upload is successful, return the name of directory/filename of the upload.
    if ($upload_success) {
        return response()->json($upload_success, 200);
    // Else, return error 400
    else {
        return response()->json('error', 400);

As we can see, we store the file using storeAs , in the storage that named public . that can be customized from config/filesystems you’ll see a public system there, I changed it to look like this:

'public' => [
    'driver' => 'local',
    'root' => public_path('uploads'),
    'url' => env('APP_URL').'/uploads',
    'visibility' => 'public',

This way, the files will be uploaded to public/uploads/ .

The Route:

In our routes/web file will add this post route which will go to our controller:

Route::post('/upload' , '[email protected]');

The View:

let’s make a blade file that contains our uploader and its javascript functions:

in head section in our blade file, we’ll add Dropzone.js dependencies:

<script src="{{url('js/dropzone.js')}}"></script>
<link rel="stylesheet" href="{{url('css/dropzone.css')}}">

Next, we’ll create the uploading form:

<form action="{{ url('/admin/upload') }}" enctype="multipart/form-data" class="dropzone" id="my-dropzone">
    {{ csrf_field() }}

Notice that we added csrf_field() . The form will not work properly without it!

now, let’s start adding the footer scripts:

First, let’s add clipboardjs script, which we’ll be using to copy the URL of the uploaded images

The first script will be the upload script:

Dropzone.options.myDropzone = {
        paramName: 'file',
        maxFilesize: 5, // MB
        maxFiles: 20,
        acceptedFiles: ".jpeg,.jpg,.png,.gif",
        init: function() {
            this.on("success", function(file, response) {
                var a = document.createElement('span');
                a.className = "thumb-url btn btn-primary";
                a.innerHTML = "copy url";

If you want to stop right here, and you don’t need to copy the URLs, you can delete the init function from the above script init:function(){...} and you’ll be done! In the above script, specifically on success function, we can see that we made a variable a which is a span that will be embedded after each uploaded thumbnail.

This span has a class thumb-url and a new attribute that called data-clipboard-text which contains the response from our upload function. Our span also has the classes btn btn-primary , those are boostrap classes, we will be using bootstrap to show the tooltip when the user click to copy the URL.

Let’s add the clipboard js:

<script src="{{url('js/clipboard.min.js')}}"></script>

Now let’s write our function:

        trigger: 'click',
        placement: 'bottom'

    function setTooltip(btn, message) {
            .attr('data-original-title', message)

    function hideTooltip(btn) {
        setTimeout(function() {
        }, 500);

    var clipboard = new Clipboard('.thumb-url');

    clipboard.on('success', function(e) {
        setTooltip(e.trigger, 'Copied!');

    clipboard.on('error', function(e) {
        setTooltip(e.trigger, 'Failed');

This way, when the user click on the copy button, it will show a tooltip that says copied! and the URL will be copied to the clipboard! I hope you find this tutorial helpful

Nice coding! 🌹