Uploading Multiple Files to Google Drive with Google App Script

I know this question is old, but after finding it and related ones, I was never able to get the multiple file upload working. So after a lot of banging my head against walls, I wanted to post a full example (.html and .gs) in case anyone in the future is looking for one to get started. This is working when I deploy it right now and will hopefully work for other people out there. Note that I just hardcoded the folder in the drive to use in the code.gs file, but that can be easily changed.

form.html:

<body>
  <div id="formcontainer">

    <label for="myForm">Facilities Project Database Attachment Uploader:</label>

    <br><br>


    <form id="myForm"> 
      <label for="myForm">Project Details:</label>
      <div>
        <input type="text" name="zone" placeholder="Zone:">
      </div>
      <div>
        <input type="text" name="building" placeholder="Building(s):">
      </div>
      <div>
        <input type="text" name="propertyAddress" placeholder="Property Address:">
      </div>
      <div>

      <label for="fileText">Project Description:</label>

          <TEXTAREA name="projectDescription" 
          placeholder="Describe your attachment(s) here:"
          style ="width:400px; height:200px;"
          ></TEXTAREA>


      </div> 
      <br>


      <label for="attachType">Choose Attachment Type:</label>
      <br>
      <select name="attachType">
        <option value="Pictures Only">Picture(s)</option>
        <option value="Proposals Only">Proposal(s)</option>
        <option value="Pictures & Proposals">All</option>
      </select>
      <br>

      <label for="myFile">Upload Attachment(s):</label>
      <br>


      <input type="file" name="filename" id="myFile" multiple>

      <input type="button" value="Submit" onclick="iteratorFileUpload()">


    </form>
  </div>

  <div id="output"></div>
<div id="progressbar">
    <div class="progress-label"></div>
</div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>

<script>

var numUploads = {};
numUploads.done = 0;
numUploads.total = 0;

// Upload the files into a folder in drive
// This is set to send them all to one folder (specificed in the .gs file)
function iteratorFileUpload() {
    var allFiles = document.getElementById('myFile').files;

    if (allFiles.length == 0) {
        alert('No file selected!');
    } else {
        //Show Progress Bar

        numUploads.total = allFiles.length;
        $('#progressbar').progressbar({
        value : false
        });//.append("<div class='caption'>37%</div>");
        $(".progress-label").html('Preparing files for upload');
        // Send each file at a time
        for (var i = 0; i < allFiles.length; i++) {
            console.log(i);
            sendFileToDrive(allFiles[i]);
        }
    }
}

function sendFileToDrive(file) {
    var reader = new FileReader();
    reader.onload = function (e) {
        var content = reader.result;
        console.log('Sending ' + file.name);
        var currFolder = 'Something';
        google.script.run.withSuccessHandler(updateProgressbar).uploadFileToDrive(content, file.name, currFolder);
    }
    reader.readAsDataURL(file);
}

function updateProgressbar( idUpdate ){
   console.log('Received: ' + idUpdate);
   numUploads.done++;
   var porc = Math.ceil((numUploads.done / numUploads.total)*100);
   $("#progressbar").progressbar({value: porc });
   $(".progress-label").text(numUploads.done +'/'+ numUploads.total);
   if( numUploads.done == numUploads.total ){
      //uploadsFinished();
      numUploads.done = 0;
   };
}
</script>

  <script>
    function fileUploaded(status) {
      document.getElementById('myForm').style.display = 'none';
      document.getElementById('output').innerHTML = status;
    }

  </script>

  <style>
    body {
      max-width: 400px;
      padding: 20px;
      margin: auto;
    }
    input {
      display: inline-block;
      width: 100%;
      padding: 5px 0px 5px 5px;
      margin-bottom: 10px;
      -webkit-box-sizing: border-box;
      ‌​ -moz-box-sizing: border-box;
      box-sizing: border-box;
    }
    select {
      margin: 5px 0px 15px 0px;
    }
    input[type="submit"] {
      width: auto !important;
      display: block !important;
    }
    input[type="file"] {
      padding: 5px 0px 15px 0px !important;
    }
#progressbar{
    width: 100%;
    text-align: center;
    overflow: hidden;
    position: relative;
    vertical-align: middle;

}
.progress-label {
      float: left;
margin-top: 5px;
      font-weight: bold;
      text-shadow: 1px 1px 0 #fff;
          width: 100%;
    height: 100%;
    position: absolute;
    vertical-align: middle;
    }
  </style>
</body>

code.gs:

function doGet() {
  return HtmlService.createHtmlOutputFromFile('form')
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function uploadFileToDrive(base64Data, fileName) {
  try{
    var splitBase = base64Data.split(','),
        type = splitBase[0].split(';')[0].replace('data:','');

    var byteCharacters = Utilities.base64Decode(splitBase[1]);
    var ss = Utilities.newBlob(byteCharacters, type);
    ss.setName(fileName);

    var dropbox = "Something"; // Folder Name
    var folder, folders = DriveApp.getFoldersByName(dropbox);

    if (folders.hasNext()) {
      folder = folders.next();
    } else {
      folder = DriveApp.createFolder(dropbox);
    }
    var file = folder.createFile(ss);

    return file.getName();
  }catch(e){
    return 'Error: ' + e.toString();
  }
}

Updated For May 2017

I updated and improved barragan's answer.

Advantages:

  1. Allows users to create a subfolder name to contain all the files uploaded during this session
  2. Ensures that these subfolders all exist within one specified folder within your Google Drive
  3. The page/form is mobile-responsive

You can start with this example just to create the script and get to know the basics.

Then you can completely replace form.html with this:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>
            Send Files
        </title>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>        
        <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
        <script>

            $(document).ready(function () {

                function afterSubfolderCreated(subfolderId) {
                    console.log(subfolderId);
                    console.log(allFiles);
                    numUploads.total = allFiles.length;
                    $('#progressbar').progressbar({
                        value: false
                    });
                    $(".progress-label").html('Preparing files for upload');
                    for (var i = 0; i < allFiles.length; i++) {
                        console.log(i);
                        sendFileToDrive(allFiles[i], subfolderId);
                    }
                }

                function sendFileToDrive(file, subfolderId) {
                    var reader = new FileReader();
                    reader.onload = function (e) {
                        var content = reader.result;
                        console.log('Sending ' + file.name);
                        google.script.run.withSuccessHandler(updateProgressbar).uploadFileToDrive(content, file.name, subfolderId);
                    }
                    reader.readAsDataURL(file);
                }

                function updateProgressbar(idUpdate) {
                    console.log('Received: ' + idUpdate);
                    numUploads.done++;
                    var porc = Math.ceil((numUploads.done / numUploads.total) * 100);
                    $("#progressbar").progressbar({value: porc});
                    $(".progress-label").text(numUploads.done + '/' + numUploads.total);
                    if (numUploads.done == numUploads.total) {                        
                        numUploads.done = 0;
                        $(".progress-label").text($(".progress-label").text() + ': FINISHED!');
                        $("#progressbar").after('(Optional) Refresh this page if you remembered other screenshots you want to add.');//<a href="javascript:window.top.location.href=window.top.location.href"> does not work
                    }
                }

                function fileUploaded(status) {
                    document.getElementById('myForm').style.display = 'none';
                    document.getElementById('output').innerHTML = status;
                }
                var numUploads = {};
                numUploads.done = 0;
                numUploads.total = 0;
                var allFiles;
                var frm = $('#myForm');
                frm.submit(function () {
                    allFiles = document.getElementById('myFile').files;
                    if (!frm.checkValidity || frm.checkValidity()) {
                        if (allFiles.length == 0) {
                            alert('Error: Please choose at least 1 file to upload.');
                            return false;
                        } else {
                            frm.hide();
                            var subfolderName = document.getElementById('myName').value;
                            $.ajax({
                                url: '',//URL of webhook endpoint for sending a Slack notification
                                data: {
                                     title: subfolderName + ' is uploading screenshots',
                                     message: ''
                                }
                            });
                            google.script.run.withSuccessHandler(afterSubfolderCreated).createSubfolder(subfolderName);
                            return false;
                        }
                    } else {
                        alert('Invalid form');
                        return false;
                    }
                });
            });//end docReady
        </script>
        <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
        <style>
            body {
                padding: 20px;
                margin: auto;
                font-size: 20px;
            }
            label{
                font-weight: bold;
            }
            input{
                font-size: 20px;
                padding: 5px;
                display: inline-block;
                margin-bottom: 10px;
                -webkit-box-sizing: border-box;
                ‌-moz-box-sizing: border-box;
                box-sizing: border-box;
            }
            .hint{
                font-size: 90%;
                color: #666;
            }
            select {
                margin: 5px 0px 15px 0px;
            }
            input[type="file"] {
                padding: 5px 0px 15px 0px;
            }
            #progressbar{
                width: 100%;
                text-align: center;
                overflow: hidden;
                position: relative;
                vertical-align: middle;
            }
            .progress-label {
                float: left;
                margin-top: 5px;
                font-weight: bold;
                text-shadow: 1px 1px 0 #fff;
                width: 100%;
                height: 100%;
                position: absolute;
                vertical-align: middle;
            }
            li{
               padding: 10px;
            }
            @media only screen and (max-width : 520px) {
                #logo {
                    max-width: 100%;
                }
            }
        </style>
    </head>
    <body>
        <p>
            <img src="" id="logo">
        </p>
        <p>This webpage allows you to send me screenshots of your dating profiles.</p>
        <ol>
            <li>
                In each of your dating apps, take a screenshot <a href="https://www.take-a-screenshot.org/" target="_blank">(how?)</a> of every part of every page of your profile.
            </li>
            <li>
                Do the same for each of your photos (at full resolution).
            </li>
            <li>
                In the form below, type your first name and last initial (without any spaces or special characters), such as SallyT.
            </li>
            <li>
                Then click the first button and select all of your screenshot images (all at once).
            </li>
            <li>
                Finally, press the last button to send them all to me!
            </li>
        </ol>
        <form id="myForm"> 
            <div>
                <label for="myName">Your first name and last initial:</label> 
            </div>
            <div>
                <input type="text" name="myName" id="myName" placeholder="SallyT" required pattern="[a-zA-Z]+" value=""> 
                </div>
                <div>
                    <span class="hint">(No spaces or special characters allowed because this will determine your folder name)</span>
                </div>            
            <div style="margin-top: 20px;">
                <label for="myFile">Screenshot image files:</label>

                <input type="file" name="filename" id="myFile" multiple>
            </div>
            <div style="margin-top: 20px;">
                <input type="submit" value="Send File(s) ➔" >
            </div>
        </form>

        <div id="output"></div>
        <div id="progressbar">
            <div class="progress-label"></div>
        </div>


    </body>
</html>

And completely replace server.gs with this:

function doGet() {
  var output = HtmlService.createHtmlOutputFromFile('form');
  output.addMetaTag('viewport', 'width=device-width, initial-scale=1');// See http://stackoverflow.com/a/42681526/470749
  return output.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function uploadFileToDrive(base64Data, fileName, subfolderId) {
  Logger.log(subfolderId);
  try{
    var splitBase = base64Data.split(','),
        type = splitBase[0].split(';')[0].replace('data:','');

    var byteCharacters = Utilities.base64Decode(splitBase[1]);
    var ss = Utilities.newBlob(byteCharacters, type);
    ss.setName(fileName);
    var subfolder = DriveApp.getFolderById(subfolderId);
    var file = subfolder.createFile(ss);
    Logger.log(file);
    return file.getName() + ' at ' + file.getUrl();
  } catch(e){
    return 'createFile Error: ' + e.toString();
  }
}

function createSubfolder(subfolderName){
  var dropbox = subfolderName + Utilities.formatDate(new Date(), "US/Eastern", "_yyyy-MM-dd");
    Logger.log(dropbox);
    var parentFolderId = "{ID such as 0B9iQ20nrMNYAaHZxRjd}";
    var parentFolder = DriveApp.getFolderById(parentFolderId);
    var folder;
    try {
      folder = parentFolder.getFoldersByName(dropbox).next();      
    }
    catch(e) {
      folder = parentFolder.createFolder(dropbox);
    }
    Logger.log(folder);
  return folder.getId();
}

Joining the pile of older answers, but this really helped me when I implemented my own file multi upload for getting files into Google Drive using the DriveApp V3 api.

I wrote and posted my own solution here: https://github.com/CharlesPlucker/drive-multi-upload/

Improvements: - Handles multiple files sequentially - Handles large > 50MB files - Validation

I wrapped my google api calls into promises since I was running into timing issues when creating a folder and immediately trying to fetch it by name reference. Each one of my promises will only resolve when its file is fully uploaded. To get that working I had to use a Promise Factory. I'll be glad to explain this code if the need arises!