0

The Google support article example under the Forms heading is broken. From the article:

If you call a server function with a form element as a parameter, the form becomes a single object with field names as keys and field values as values. The values are all converted to strings, except for the contents of file-input fields, which become Blob objects.

I tested this by passing a Form element containing 5 text inputs and a file, then logging Object.keys() on the form object. It returned only the 5 text fields, the file was stripped from the form object. Attempting to assign the file blob directly returned Exception: Invalid argument: blob

How do I pass the file blob from the client-side Form to the server-side Apps Script?

EDIT: To clarify, I also copy-pasted the example provided by Google verbatim. It errors with Exception: Invalid argument: blob.

To reproduce:

  1. Create new Google Apps Script project
  2. Index.html contents:
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>
  1. Code.gs contents:
function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}
  1. Publish as Web App
  2. Submit the form with any file
  3. Observe error in View -> Stackdriver Logging -> Apps Script Dashboard
6
  • As the documentation states, you should send form element not form object Commented Aug 20, 2019 at 16:12
  • I used the wrong word...I updated the question. I passed the HTML Form element. Commented Aug 20, 2019 at 16:19
  • Show how you passed the element with code. Provide minimal reproducible example. Is this Exception: Invalid argument: blob. client side or server side? Commented Aug 20, 2019 at 16:34
  • Your code works perfectly in my test uploading a .pdf file. What is the mime type of the file you're uploading? Commented Aug 21, 2019 at 7:48
  • @AndresDuarte I've tried various (txt, xlsx, png), nothing works. Hearing the exact same code works for others now leads me to believe there is some sort of corporate restriction going on at my company. (◔_◔) Commented Aug 21, 2019 at 17:42

2 Answers 2

2

Here's an example:

html:

<!DOCTYPE html>
<html>
  <head>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  <script>
  function fileUploadJs(frmData) {
    document.getElementById('status').style.display ='inline';
    google.script.run
      .withSuccessHandler(updateOutput)
      .uploadTheFile(frmData)
  }

  function updateOutput(info)  {
    var br='<br />';
    var outputDiv = document.getElementById('status');
    outputDiv.innerHTML = br + 'File Upload Successful.' + br + 'File Name: ' + info.name + br + 'Content Type: ' + info.type + br + 'Folder Name: ' + info.folder;
  }

  console.log('My Code');
</script>
<style>
  body {background-color:#ffffff;}
  input{padding:2px;margin:2px;}
</style>

  </head>
  <body>
    <h1 id="main-heading">Walking Tracks</h1>
    <h3>Upload GPS Tracks Files</h3>
    <div id="formDiv">
      <form id="myForm">
        <input name="fileToLoad" type="file" /><br/>
        <input type="button" value="Submit" onclick="fileUploadJs(this.parentNode)" />
      </form>
    </div>
  <div id="status" style="display: none">
  <!-- div will be filled with innerHTML after form submission. -->
  Uploading. Please wait...
  </div>  
  <div id="controls">
      <input type="button" value="Close" onClick="google.script.host.close();" />
  </div>
</body>
</html>

server code:

function uploadTheFile(theForm) {
  var fileBlob=theForm.fileToLoad;
  var fldr = DriveApp.getFolderById('FolderId');
  var file=fldr.createFile(fileBlob);
  var fi=formatFileName(file);
  var fileInfo={'name':fi.getName(),'type':fileBlob.getContentType(), 'size':fileBlob.getBytes(), 'folder':fldr.getName()};
  return fileInfo;
}
Sign up to request clarification or add additional context in comments.

2 Comments

I updated your getFolderById method to retrieve something that exists in my drive, but it still does not work for me. Same issue, fileBlob is null...theForm does not contain a key for fileToLoad. I am now wondering if this is a corporate security policy at my org...or maybe our agreement with Google restricts certain functions somehow? Same code works for others, but not in my org. Doesn't make sense.
This a working function. I use it nearly every day for uploading GPS track files from DropBox
1

I can confirm that this doesn't work in G-Suite Enterprise. I don't know why because I cannot find documentation that says how Google is serializing the data. It could be a browser/computer security setting or something in G-Suite.

However, there is an easier way to accomplish your need. You can use a Google Form with a file upload question and then create an on form submit trigger/event on it to copy the file to a team/shared drive. Here is sample code if you want to attach the trigger to the Google Form itself:

// ID of the destnation folder to save the file in
var destinationFolderID = "10gkU_2V9iYy-VKudOCOjydEpoepPTgPv"

function saveFileToTeamDrive(e)
{
  // a place to save the URL of the uploaded file
  var fileID;

  // go through all of the responses to find the URL of the uploaded file
  e.response.getItemResponses().forEach(function(itemResponse){
    // once we find the question with the file
    if(itemResponse.getItem().getTitle() == "File Upload Test")
    {
      // get the file ID from the response
      fileID = itemResponse.getResponse();
      return;
    }
  });

  // stop if we didn't have one
  if(!fileID.length) return;

  // get the first index in the array
  fileID = fileID[0];

  // get the file
  var file = DriveApp.getFileById(fileID);

  // get the destination folder
  var destinationFolder = DriveApp.getFolderById(destinationFolderID);

  // make a copy
  var newFile = file.makeCopy(destinationFolder);

  Logger.log(newFile.getUrl());
}

You can also attach to the on form submit event of a Google Sheet that is linked to a Google Form. I find that way easier cause the Google Sheet on form submit trigger/event includes a JSON of the question/answers so you don't have to iterate all of them to find it. It also means you can re-run a submission if it failed.

WARNING

One important note, if you do either of these things do not give anyone else edit access to the code. This is because as soon as you create and authorize the trigger, anyone who has edit access to the code would be able to use it to gain access to your Google Drive (and anything else the trigger is authorized for). Please see securing a Google Apps Script linked to an authorized trigger so others can edit for more information.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.