2

this is my first time submitting a question, so please help me if I could ask this better.

I want to:

  • create a folder
  • in a google team drive
  • nested in an existing Team Drive folder “path”
  • from Google Apps Script, which can be in a Script file or attached to a Google Sheet

I believe these resources tell me how to create the Team Drive folder:

Plan A: Create a folder using GAS. After some research, it’s my understanding that plain GAS cannot create a folder in Team Drive yet.

[18-03-15 14:55:23:674 EDT] Execution failed: Cannot use this operation on a Team Drive item. (line 139, file "utils") [1.618 seconds total runtime]

//The folder with the current name doesn't exist - create it in the parent folder
var newFolder = DriveApp.createFolder(nextFolderName);
currFolder.addFolder(newFolder);

If I’m wrong and there is a simple way to make the addFolder() code work with Team Drive, then that would be a win.

Update 1: You can create a folder in Team Drive with plain GAS. It is folder moving that is restricted. This works:

//The folder with the current name doesn't exist - create it in the parent folder
var newFolder = currFolder.createFolder(nextFolderName);

So Plan B: Now, I’m trying to create the folder using Google Drive REST API v3. However, I’m only getting an Untitled file in my personal drive when I run my code, even though TEAM_ROOT_PATH_FOLDER_ID is in a Team Drive. I think my problem is getting the right information to UrlFetchApp.fetch. It’s like the request body isn’t being read.

References:

Here’s my code for creating the folder using Google Apps Script and the Drive REST API:

var TEAM_ROOT_PATH_FOLDER_ID = "diPcXqabvgpX4tcZJZ-ms2Wg0bXf1Fem"; //TODO fill in based on parent folder

function test() {
  createTeamDriveFolder(TEAM_ROOT_PATH_FOLDER_ID);
}


function createTeamDriveFolder(parentFolderId) {

  //BUILD THE URL
  var baseUrl = "https://www.googleapis.com/upload/drive/v3/files/";

  var urlParams = { //TODO correct for this function (after you figure this out)
    supportTeamDrives: true
    //,alt: "json"
    ,uploadType: "media"
  };

  //CODE CREDIT: https://ctrlq.org/code/20514-list-team-drives-google-drive-apps-script
  var queryString = Object.keys(urlParams).map(function(p) {
    return [encodeURIComponent(p), encodeURIComponent(urlParams[p])].join("=");
  }).join("&");

  var apiUrlStr = baseUrl + "?" + queryString;


  //BUILD THE REQUEST - HEADERS AND BODY
  var token = ScriptApp.getOAuthToken();
  var requestBody = {
    mimeType: "application/vnd.google-apps.folder"
    ,name: "TeamDriveTestFolder"
    ,"parents": [parentFolderId]
  };

  var requestBodyStr = JSON.stringify(requestBody);
  Logger.log("requestBody: " + requestBodyStr);

  var requestParams = {
    method: "POST"
    ,contentType: "application/json"
    ,contentLength: requestBodyStr.length
    ,headers: {"Authorization": "Bearer " + token}
    ,payload: requestBodyStr
  };

  var response = UrlFetchApp.fetch(apiUrlStr, requestParams);

  Logger.log(response.getResponseCode());
  Logger.log(response.getContentText());
}

However, I can get a folder to appear in my personal drive (still wrong) when I run the REST call through the API test:

supportsTeamDrives > true

WORKS (TeamDriveTestFolder folder created in personal drive root):

{
  "mimeType": "application/vnd.google-apps.folder",
  "name": "TeamDriveTestFolder"
}

FAILS (404):

{
  "mimeType": "application/vnd.google-apps.folder",
  "name": "TeamDriveTestFolder",
  "parents": ["diPcXqabvgpX4tcZJZ-ms2Wg0bXf1Fem"]
}

I appreciate any assistance in cracking this Team folder creation.

Wes Neal
  • 23
  • 1
  • 5
  • Use the `Drive` advanced service, rather than `UrlFetchApp` – tehhowch Mar 27 '18 at 22:19
  • Also, the reason you get a 404 is that your `"parents"` specification is incorrect. `parents` is supposed to be an array of folders that contain the file, where the minimum definition of such a folder is its unique id: `{id: "the folder id"}`. In other words, `{mimeType: ..., name: ..., parents: [ {id: "the parent folder id"} ] }` should work. Make sure you allow Drive to include Team Drive items in the response. – tehhowch Mar 27 '18 at 23:44

3 Answers3

4

For your 3rd point "Nested in an existing team drive folder", you can directly use GAS. Following is the code snippet,

var parentFolder=DriveApp.getFolderById('folderId');
var newFolder=parentFolder.createFolder('name'); //replace name with Folder name.

Here you need to pass parent folder ID in folderId field. You can find this FolderID in the URL from Team Drive. In the below image '0AFmY7fi4434bUk9PVA' is the FolderID. enter image description here

Darpan Sanghavi
  • 1,443
  • 2
  • 17
  • 32
  • Thanks [Darpan Sanghavi](https://stackoverflow.com/users/1423927/darpan-sanghavi). That approach made my original code work without having to use the Advanced Drive Service. The problem with Team Drives and plain GAS is apparently moving folders, but the direct folder creation in the right place works. Interestingly, I noticed folder traversal takes about 2 seconds, as does folder creation, so building a path works but takes 10-20 seconds. Watch those quotas! – Wes Neal Mar 28 '18 at 21:06
  • @WesNeal : Glad to hear that. Yes plain GAS does not provide API to move folder/files. You can create a new file and remove from original place. It again depends on use case and run time quota. – Darpan Sanghavi Mar 29 '18 at 04:26
  • @WesNeal:If the response answers your question please use the "tick" option to mark that response as the answer. If not, you should edit your question and clarify it so people have an opportunity to give you a better answer. – Darpan Sanghavi Mar 30 '18 at 11:19
  • @WesNeal : Have you found your answer for moving files from local drive to team drive folder? – Darpan Sanghavi Apr 06 '18 at 10:46
2

Using the Drive advanced service (V2 API reference), it is fairly straightforward to create a folder, even in a Team Drive:

function getNewRootLevelFolderInTeamDrive(newTitle) {
  if(!newTitle) throw new TypeError("Missing argument for new folder's title");

  // Assumes you are working in the first Team Drive.
  var td = Drive.Teamdrives.list().items[0];
  var params = {
    supportsTeamDrives: true,
    includeTeamDriveItems: true,
  };
  // Making the folder uses Drive.Files.insert(). To create a folder, use the folder
  // MimeType, and ensure the resource is otherwise appropriately specified. You can
  // pass content, but it will be discarded, so just use `null` for the mediaBody / Blob.
  var metadata = {
    parents: [td],
    mimeType: MimeType.FOLDER,
    title: newTitle,
  };
  return Drive.Files.insert(metadata, null, params);
}

While the above method creates (and returns) a folder, it forces that folder to be a root-level folder in the first Team Drive. This is a pretty flimsy "force" though, as we can generalize the new folder to be the child of any given folder:

function makeSubFolder(parentId, title) {
  if(!parentId) throw new TypeError("Expected a string ID to be passed, identifying the parent folder in which a folder will be created.");
  if(!title) title = "Random Folder Title";

  var params = {
    supportsTeamDrives: true,
    includeTeamDriveItems: true,
    corpora: /* Review the Drive API documentation on corpora and choose the applicable option. */
  };
  var metadata = {
    parents:[],
    mimeType: MimeType.FOLDER,
    title: title,
  };
  // Get the parent folder. Because we explicitly allow team drive items, and
  // declare support for team drives, team drive folders can be used.
  var folder = Drive.Files.get(parentId, params);
  if(folder.mimeType !== MimeType.FOLDER)
    throw new TypeError("Input parent folder ID is a file of type='" + file.mimeType + "'. Folders must be type='" + MimeType.FOLDER + "'.");

  // Make the new folder in the parent folder.
  metadata.parents.push(folder);
  return Drive.Files.insert(metadata, null, params);
}

An example function using them both:

function foo() {
  var rootTDFolder = getNewRootLevelFolderInTeamDrive("stack_overflown");
  var folder = makeSubFolder(rootTDFolder.id, "a subfolder of 'stack_overflown'");
  console.log(rootTDFolder);
  console.log(folder);
}
tehhowch
  • 9,645
  • 4
  • 24
  • 42
  • this code was pretty brilliant. I'm still having a hard time turning the REST API spec into the right params structure for Advanced Drive Service calls, like Drive.Files.insert(resource, mediaData, optionalArgs). Were you just using the [Files: insert](https://developers.google.com/drive/v2/reference/files/insert) reference, or was there something that demonstrated how to build the param objects for the Advanced Drive Service in GAS? This [Enabling Team Drives](https://developers.google.com/drive/v3/web/enable-teamdrives) article also helped me understand some of your steps. – Wes Neal Mar 28 '18 at 21:21
  • @WesNeal if you also look at the client library examples, they will help with using the advanced service, mostly because the advanced service is another client library. In general, the `resource` refers to metadata about the thing, the body/mediafile refers to the `Blob` of actual data, and the `optionalArgs` refers to config that controls the method itself, such as query parameters, page tokens, etc. – tehhowch Mar 28 '18 at 23:50
1

The MakeSubfolder is an amazing solution. Thinking about changing it to be a global alternative to new rootFolders as well.

If there's no parentFolder indicated just make it on Root.

Also it's amazing that works well with Drive and SharedDrive.

tnks a lot.

Ian Thomaz
  • 111
  • 1
  • 2
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). – Argyll Aug 26 '21 at 19:37