I recently needed a way to easily search for files in Google Drive related to specific projects, the file template version, and whether it was an archived file or a live file with Google Apps Script.
In most cases, I needed to reference a bunch of Google Sheets built upon a template Google Sheet that was generated for a project, but also other Google files and non-Google file types in Google Drive.
The problem is that sometimes files can be edited by mischievous little imps 👿 that can mess with naming conventions and even administrative sections in files while the user is away 😉.
This means that titles and file data can be an unreliable way of searching for files. Further, searching within files for target text, especially over a large list of files, can be incredibly time-consuming programmatically.
There is an excellent way of managing this with Google’s Drive Labels API. Unfortunately, as of the writing of this tutorial, label creation is only available to Google Admins. However, there may be a change to this in the future with access to label creation at the Shared Drive level – take a look at this screenshot:

So what’s the alternative?
After sifting through the deepest darkest reaches of Google Drive API documentation, I stumbled across adding custom file properties to any file in your Google Drive.
Custom File Properties
Custom File Properties allow us to add our own searchable or queryable key-value pairs as metadata to any file in our Google Drive. It can only be edited programmatically, preventing these little imps mentioned above from messing with them.
There are two types of properties:
properties
: Visible to all applications.appProperties
: Visible only to the app using the file (Great for Google Workspace Add-ons and Google Workspace Editor Add-ons for file tracking).
Custom file properties are limited to:
- A maximum of 100 custom properties per file from all sources.
- A maximum of 30
appProperties
per file for per application and - 30
properties
per file. - A maximum of 124 bytes per property. This is the combined total of both the key and value of each property.
The Example
We’ll be following an example of generating and querying a set of properties for a number of files.
To play along, you can set up your Google Apps Script file by:
- https://script.new/ : Create a new Apps Script File
- Add Drive API Advanced Service Version 3:
- Create a file called
Setup.gs
. - Paste the following:
123456789101112131415/*** Creates 7 files from a template file.*/function setupDemoFiles() {const folder = DriveApp.getFolderById(REF.FOLDER_ID)const sourceFile = DriveApp.getFileById(REF.TEMPLATE_FILE_ID)for(let i = 0; i < 10; i++){const fileNum = i + 7sourceFile.makeCopy('Test Metadata' + fileNum, folder)}}
- In your main
Code.gs
file add the globalREF
constant variable:1234const REF = {FOLDER_ID: "12P1<<YOUR_TEST_FOLDER_ID>>0yA3",TEMPLATE_FILE_ID: "14auvx<<YOUR_TEMP_FILE_ID>>XW3Y448",} - Create a test folder and replace the ID in the
FOLDER_ID
with its ID. - Create a template file (I am using a blank Google Sheet here) and replace the
TEMPLATE_FILE_ID
with your newly minted template file ID. - Go back to your
Setup.gs
file and run the functionsetupDemoFiles()
This will generate 7 files that we can add our property data to.
Update & Create Custom Google Drive File Properties with Apps Script
We can update a single file in Google Drive by providing the file ID and an object of key-value pairs and passing it through the Drive Files update method.
This approach will either update or add new properties to a file. Any existing property with the same name as the new property key will be replaced.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// ####### UPDATE CUSTOM FILE PROPERTIES ####### function test_updateCustomFileProperties() { const properties = { 'project': 'metadata Test', 'version': 'v002', 'status': 'live' } updateCustomFileProperties(REF.TEMPLATE_FILE_ID, properties) } /** * Updates or creates custom file properties that are searchable * when querying files. * * * @param {string} fileId * @param {Object} object of string properiteis to populate. */ function updateCustomFileProperties(fileId, properties) { const resource = { 'properties': properties } let file try { file = Drive.Files.update(resource, fileId, null, { 'fields': 'id,properties' }) } catch (e) { throw new Error(`Failed to create custom file property: ${e}`) } console.log(file) return file } |
The Test Function
Lines 1-7
In our test function, test_updateCustomFileProperties()
we are providing a properties object containing 3 properties that we want to add to our file.
We are also referencing our template file global variable for this example.
updateCustomFileProperties()
Lines 13 – 36
The function
The updateCustomFileProperties()
function takes a file ID string and a properties object of key-value pairs as its two arguments. Line 21
The Resource Object
Next, we create a resource
object containing our properties. You may also wish to update other metadata items of the file or even the data of the file during this process. Lines 23-25
Executing the ‘Update’ Method
In our try-catch statement, we then make a call to the Drive.Files.update() method to update our metadata properties.
The updates()
method requires 4 arguments:
- Resource: The target resource object to change any metadata items in the file, including our custom parameters.
- File ID: The file ID of the file to update.
- File content blob: For us, this is set to
null
because we do not wish to change the content of the file. However, you can create blob data to update the file contents here. - Optional parameters: Here, you can add optional parameters. For us, we want to see both the
id
and theproperties
of the file returned after we update the file. To do this, we use the ‘fields’ property.
Commonly, you may need to update a shared drive and will need your optional parameters object to look a little more like this:
12345const optionalArgs = {'fields': 'id,properties','supportsAllDrives': true,'includeItemsFromAllDrives': true,}
If successful, the update will return the file’s ID and the full properties object, including the properties that you just added or updated.
Here is an example response:
1 2 3 4 5 6 7 8 |
{ id: '14auvxApYSHl7bZmf655eVsnAVVvBEME8Z8xHXW3Y448', properties: { status: 'live', project: 'metadata Test', version: 'v002' } } |
Search Files By Custom File Properties in Google Drive with Apps Script
Now that we have created our first custom file property, we want to be able to query it. Fortunately, the Google Drive API allows us to do this with the Drive.Files.list() method.
Google Drive API Files list comes packed with a lot of options, which can make things a little confusing to set up for your needs. For this tutorial, we are going to keep things simple to focus on working with properties, but if you want to dive into queries, you should check out this tutorial:
List all files and folders in a selected folder’s directory tree in Google Drive: Apps Script
On with the code…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// ####### SEARCH FILES BY CUSTOM FILE PROPERTIES ####### function test_searchFilesByCustomFileProperties() { const properties = { 'project': 'metadata Test', 'version': 'v002', 'status': 'live' } searchFilesByCustomFileProperties(properties) } /** * Searches files by custom file properties * * @param {Object} searchQuery * @returns {Object} - {files: [{properties, id, name}]} */ function searchFilesByCustomFileProperties(searchQueries) { const queriesArray = Object.entries(searchQueries) const queriesArrayLength = queriesArray.length const propertiesQueries = queriesArray.reduce((acc, prop, idx) => { const key = prop[0] const val = prop[1] acc += `properties has {key='${key}' and value='${val}'}` if (idx !== queriesArrayLength - 1) { acc += " AND " } return acc }, "") console.log(propertiesQueries) const payload = { 'fields': 'files(id,properties,name)', 'supportsAllDrives': true, 'includeItemsFromAllDrives': true, 'corpora': 'allDrives', // 'q':`properties has {key='project' and value='metadata Test'}`, // Basic Query 'q': propertiesQueries + ' and trashed=false' } let files try { files = Drive.Files.list(payload) } catch (e) { throw new Error(`Failed to query files by property: ${e}`) } console.log(JSON.stringify(files, null, " ")) return files } |
The Test Function
Lines 1-10
The function, test_searchFilesByCustomFileProperties(
)
, sets the properties to be queries.
In the example, we are querying all properties by both their key and value pairs. However, we could just query by any one of these properties on their own. For example, we may wish to find all the files under the project, ‘metadata Test’, that have the status ‘live’ and not care about the version.
searchFilesByCustomFileProperties()
Lines 12 – 55
The Function
The searchFilesByCustomFileProperties()
function takes a single object of properties to query as its argument.
You can provide some or all properties as your query to either broaden or narrow in your search query.
Generate the query string
Lines 20-33 and line 43
We can create a basic search query for the custom file properties in our Google Drive with the following query string:
properties has {key='project' and value='metadata Test'}
Here, we are searching for any file that contains the key, ‘project,’ that also contains the value ‘metadata Test’. To broaden your search query, you can also omit the and
and value
data to only look for files that contain the property key.
To search by multiple property queries, we need to join them with an and
.
Our first task is to convert our searchQueries
object to a 2d array of key-value pairs (line 20). We will also grab the length while we are here (line 21). So our example data would look like this:
1 2 3 4 5 |
const queriesArray = [ ['project','metadata Test'], ['version','v002'], ['status','live'], ] |
Next, we will use a JavaScript reduce method to iterate over each query array item and generate a string of search queries for each one, adding AND
between each query except for the last one (lines 23-35).
Finally, we should probably ensure that we are not searching for trashed files with trashed=false
(line 43).
Our query string would then look like this:
1 2 3 4 |
'properties has {key='project' and value='metadata Test'} AND properties has {key='version' and value='v002'} AND properties has {key='status' and value='live'} AND trashed=false' |
Generate the Payload
lines 37-44
The Drive Files list() method takes a payload of arguments. In our example, we are including:
fields
: Setting the fields to return the file ID, properties object and the name of the file. You can also display other file data like the creation date, edit date, the creator of the file, next page token if you expect a large array of results and a whole lot more.supportAllDrives
: Setting this to true allows us to access shared drives so long as we …includeItemsFromAllDrives
: to ensure we query data from all drives.corpora
: Setting this toallDrives
allows us to not only see the files we created in a shared drive but also those created by others.q
: The query.
You can modify these arguments to better match your needs by looking at the documentation.
Executing the list request
Finally, we call Drive.Files.list() with our payload.
If successful, this will return an object that includes a files property containing an array of files that match the query.
It should look a little like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
{ "files": [ { "name": "Test Metadata 1", "properties": { "project": "metadata Test", "version": "v002", "status": "live" }, "id": "14auv<<FILE_IDZZZZ>>XW3Y448" }, { "name": "Test Metadata", "id": "14JuAV<<FILE_IDZZZZ>>CWWvo", "properties": { "version": "v002", "status": "live", "project": "metadata Test" } }, { "id": "1mm2JA<<FILE_IDZZZZ>>z0O_Q", "properties": { "status": "live", "project": "metadata Test", "version": "v002" }, "name": "Test Metadata7" }, { "properties": { "project": "metadata Test", "version": "v002", "status": "live" }, "name": "Test Metadata8", "id": "1awx<<FILE_IDZZZZ>>XP3sY" }, { "properties": { "version": "v002", "project": "metadata Test", "status": "live" }, "id": "1H-N<<FILE_IDZZZZ>>zfFJdM", "name": "Test Metadata 3" }, { "name": "Test Metadata 4", "id": "1mguy<<FILE_IDZZZZ>>pky0", "properties": { "status": "live", "version": "v002", "project": "metadata Test" } }, { "name": "Test Metadata 5", "properties": { "version": "v002", "project": "metadata Test", "status": "live" }, "id": "1z6kaw<<FILE_IDZZZZ>>yUdWY" }, { "name": "Test Metadata 6", "id": "1wFR<<FILE_IDZZZZ>>LV7cM", "properties": { "version": "v002", "status": "live", "project": "metadata Test" } }, { "properties": { "status": "live", "project": "metadata Test", "version": "v002" }, "id": "1-VfV<<FILE_IDZZZZ>>Uefg8w", "name": "Test Metadata9" }, { "id": "1QQo4Sk7j<<FILE_IDZZZZ>>3lYhOS6e3fs", "properties": { "project": "metadata Test", "status": "live", "version": "v002" }, "name": "Test Metadata 2" } ] } |
Keep in mind that if you expect a larger number of files, then you should also set a field to display the next page token and then make another request for the next page.
If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.
Batch Updating Custom File Properties for Multiple Files with Apps Script
Now, if you are setting up a bunch of files to contain custom file properties or updating the files you found from your properties search query, you will want to update them quickly.
Clearly, iterating over each target file to run Drive.Files.update()
when you have thousands of files is going to take way too long.
Fortunately, the paragon of Google Apps Script development, Kanshi Tanaike, has created a helper function that allows us to batch update our update requests all in one batch or a chunk of batches at a time.
We will need to change the request type from a Drive API request to a HTML request with URL FetchApp but this is relatively simple for our purposes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
// ####### CREATE BATCH UPDATE OBJECT: CUSTOM FILE PROPERTIES ####### function test_createBatchUpdateObject_CustomFileProperties() { const properties = { 'project': 'metadata Test', 'version': 'v002', 'status': 'live' } const resp = createBatchUpdateObject_CustomFileProperties(REF.FOLDER_ID, properties) console.log(resp) } /** * Creates a batch update object ofr Custom File Properties * * @param {string} folderId - source folder id. * @param {Object} properties - properties to add to the file. * * @returns {String} batch response data receipt. */ function createBatchUpdateObject_CustomFileProperties(folderId, properties) { let list try{ list = Drive.Files.list( { q: `'${folderId}' in parents and trashed=false`, fields: 'files(id, name)', supportsTeamDrives:true, supportsAllDrives:true, } ).files; }catch(e){ throw new Error(`Failed to retrieve files in folder with id '${folderId}': ${e}`) } console.log(list) const baseURL = 'https://www.googleapis.com/drive/v3/files/' const optionalArgs = '?supportsAllDrives=true&supportsTeamDrives=true' const requests = list.map(({ id }) => { return { method: "PATCH", endpoint: `${baseURL}${id}${optionalArgs}`, requestBody: { properties } } }) const object = { batchPath: 'batch/drive/v3', requests } const resp = batchRequests(object) if (resp.getResponseCode() !== 200) { throw new Error(res.getContentText()); } return resp.getContentText() } /** * Create a request body of batch requests and request it. * * @author Kanshi Tanaike - the legend!!!! * @see https://cloud.google.com/blog/topics/developers-practitioners/efficient-file-management-using-batch-requests-google-apps-script * @param {Object} object Object for creating request body of batch requests. * @returns {Object} UrlFetchApp.HTTPResponse */ function batchRequests(object) { const { batchPath, requests } = object; const boundary = "sampleBoundary12345"; const lb = "\r\n"; const payload = requests.reduce((r, e, i, a) => { r += `Content-Type: application/http${lb}`; r += `Content-ID: ${i + 1}${lb}${lb}`; r += `${e.method} ${e.endpoint}${lb}`; r += e.requestBody ? `Content-Type: application/json; charset=utf-8" ${lb}${lb}` : lb; r += e.requestBody ? `${JSON.stringify(e.requestBody)}${lb}` : ""; r += `--${boundary}${i == a.length - 1 ? "--" : ""}${lb}`; return r; }, `--${boundary}${lb}`); const params = { muteHttpExceptions: true, method: "post", contentType: `multipart/mixed; boundary=${boundary}`, headers: { Authorization: "Bearer " + ScriptApp.getOAuthToken() }, payload, }; return UrlFetchApp.fetch(`https://www.googleapis.com/${batchPath}`, params); } |
The Test Function
Lines 3-13
Our test function hasn’t changed all that much from our single-item update. We will be setting the properties that we want to add, and then updating all files in our target folder to have the same properties.
createBatchUpdateObject_CustomFileProperties()
Lines 15-58
This function will update each file in a target folder to contain the properties provided to it.
You may wish to tweak this function to do some other things:
- Change the property values based on some other factor. Say, if any files have a last edit date longer than 90 days, then set
status
toarchive
. - Change the payload to how you draw the files. You may wish to extract all the files from an entire shared drive or traverse a particular directory.
We’ll keep things simple here and update all files in our target folder.
The function
The function, createBatchUpdateObject_CustomFileProperties()
, takes two arguments:
folderId
: the folder ID of the directory that you want to get your files from. Note that this will only extract files from one level of folders.properties
: The properties object that you will be updating.
The function will then return a string receipt of what was returned and updated from the batch request.
Extracting the Files from the Folder
Lines 24 – 26
Here we use the Drive.Files.list() method again and ensure that we are also looking in Shared Drives, just in case.
Our query this time is set to query and retrieve any file that has the parent folder of our target folder and is not a trashed file.
This will return an array of files containing the file name and ID of the found files using the fields
property. Again, you may also need to get the next page token field to iterate over larger files.
Creating the Batch Request
Lines 41 – 49
Now that we have our list of files to update, we need to build our batch request HTML object.
We can build this by demoing a build in the documentation API build too to the right of the documentation reference. Line 41
First, we set our base URL and then add our optional arguments starting with a question mark, with each argument separated by an ampersand. Line 43
Now we can iterate over our list of files and map an arrange of requests feeding in our payload. Here, we add an HTML method of PATHCH
with an endpoint URL generated with our base URL, file ID and optional arguments. This will then return an array of requests for the batch update function. Line 41 – 49
Sending and retrieving the batch request
Lines 51 – 57
Finally, we can send off our batch request object. We will first create an object variable containing our batch path for Google Drive and our array of requests.
Then we will call Tanaike’s batchRequests()
as the object of the argument. This object returns a URL Fetch App HTTP Response from the function, where we can check if an error occurred from the getResponseCode()
method and display the receipt string from the request with get ConentText()
.
batchRequest()
This glorious function, created by Kanshi Tanaike, sends a batch request for any Google API type. It is best suited for post requests, and you will need to be mindful of managing large requests yourself, where you may exceed the batch request limitations:
- Batch Request Limit: Exceeding 100 calls in a single batch request may lead to errors.
- Inner Request URL Length: Each individual request URL within a batch must not exceed 8,000 characters.
- No Batch Media Operations: Google Drive does not support batch operations for uploading or downloading media files.
- No Batch File Exports: Batch processing is not available for exporting files from Google Drive.
Here is Tanaike’s guide for more information on this function, along with some benchmarks and samples.
Efficient File Management using Batch Requests with Google Apps Script
Conclusion
As you can see, using custom file properties in Google Drive with Drive API advanced service and Google Apps Script can be an extremely handy way of storing metadata for your files for better file querying and management.
I would love to see how you would use this in your own project, and it would provide a great deal of inspiration for other users. Please consider commenting below.
If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.
~ Yagi