Add searchable tags to files via a Sheet
Maintain metadata Drive doesn't support natively — search-friendly tags for every Northwind file.
Published Aug 13, 2025
Google Drive has folders and it has full-text search, but it has no real
tagging. Northwind wants to mark files as client-acme, invoice or
archive-2025 and pull every matching file in one go — and Drive simply cannot
do that natively. Stuffing keywords into filenames is the usual workaround, and
it makes filenames an unreadable mess.
This pair of functions keeps the tags in a Sheet instead. One sheet maps a file
ID to a comma-separated list of tags. addTag keeps that list tidy, and
findFilesByTag reads it back to return every file carrying a given tag. The
filenames stay clean; the tags stay searchable.
What you’ll need
- A Google Sheet — the tag index — with two columns:
fileIdin column A andtags(comma-separated) in column B. Put headers in row 1. - The Drive file IDs of the files you want to tag. A file ID is the long string
in its URL between
/d/and/edit.
The script
// The spreadsheet that maps file IDs to their tags.
const TAGS_SHEET_ID = '1abcFileTagsId';
/**
* Returns every Drive file tagged with the given tag.
*
* @param {string} tag The tag to search for (case-insensitive).
* @return {File[]} An array of matching DriveApp File objects.
*/
function findFilesByTag(tag) {
// 1. Read the tag index, dropping the header row.
const [, ...rows] = SpreadsheetApp.openById(TAGS_SHEET_ID)
.getSheets()[0]
.getDataRange()
.getValues();
const wanted = tag.toLowerCase();
// 2. Keep rows whose tag list contains the wanted tag, then resolve
// each file ID to a real Drive file.
return rows
.filter((row) => {
const tags = String(row[1])
.toLowerCase()
.split(',')
.map((s) => s.trim());
return tags.includes(wanted);
})
.map((row) => DriveApp.getFileById(row[0]));
}
/**
* Adds a tag to a file in the index. Creates a row if the file is not
* yet listed, and never adds a duplicate tag.
*
* @param {string} fileId The Drive file ID to tag.
* @param {string} newTag The tag to add.
*/
function addTag(fileId, newTag) {
const sheet = SpreadsheetApp.openById(TAGS_SHEET_ID).getSheets()[0];
const values = sheet.getDataRange().getValues();
// 1. Find the row for this file, if it already has one.
const rowIndex = values.findIndex((row) => row[0] === fileId);
// 2. File not in the index yet — append a fresh row.
if (rowIndex === -1) {
sheet.appendRow([fileId, newTag]);
return;
}
// 3. File already listed — read its existing tags.
const existing = String(values[rowIndex][1] || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
// 4. Add the new tag only if it is not already there, then write back.
if (!existing.includes(newTag)) {
existing.push(newTag);
sheet.getRange(rowIndex + 1, 2).setValue(existing.join(', '));
}
}
How it works
findFilesByTagopens the tag index and reads every row, using array destructuring to skip the header.- For each row it splits the column B tag list on commas, trims and lower-cases each tag, and keeps the row only if the wanted tag is in it — so the search is case-insensitive.
- It maps each surviving file ID through
DriveApp.getFileByIdand returns the resultingFileobjects, ready to rename, move or share. addTagopens the same sheet and usesfindIndexto locate the row for the given file ID.- If no row exists, it appends a new one with the file ID and the single tag.
- If the file is already listed, it parses the existing comma-separated tags, adds the new one only when it is not already present, and writes the joined list back to column B — so tags never duplicate.
Example run
Start with this tag index:
| fileId | tags |
|---|---|
1aaa... | client-acme, invoice |
1bbb... | client-acme, brief |
Call addTag('1bbb...', 'archive-2025'). Column B of the second row becomes
client-acme, brief, archive-2025.
Call addTag('1ccc...', 'invoice') for a file not yet listed. A new row is
appended: 1ccc... | invoice.
Now findFilesByTag('invoice') returns the Drive files for 1aaa... and
1ccc... — the two files carrying that tag.
Run it
These are helper functions you call from other scripts, or test directly:
- Paste the script into an Apps Script project and set
TAGS_SHEET_ID. - To tag a file, run
addTagwith a file ID and tag — temporarily hard-code the arguments, or call it from your own code. - To search, call
findFilesByTag('your-tag')and loop over the returned files, for example to log their names:
function listTaggedFiles() {
findFilesByTag('client-acme').forEach((file) => {
Logger.log(file.getName());
});
}
Watch out for
- The index can drift from reality. If a file is deleted in Drive, its row
stays in the sheet and
DriveApp.getFileByIdwill throw whenfindFilesByTagreaches it. Add a cleanup pass, or wrap the lookup in atry/catch. - Tag matching is exact after trimming and lower-casing.
client-acmeandclient_acmeare different tags — agree on a naming convention up front. findFilesByTagresolves every matching file ID on each call. On a large index that means many Drive calls; cache results if you search often.- There is no locking. Two people running
addTagon the same file at once can overwrite each other’s change. For low-volume tagging this is fine; for heavy use, serialise writes withLockService. getDataRangereads the whole sheet every call. With thousands of rows this gets slow — consider batching reads if the index grows large.
Related
Build a recurring file-delivery system
Drop a fresh report file into a Northwind client folder weekly — they don't even ask.
Updated Dec 15, 2025
Build a Drive search index in Sheets
Make Northwind's file metadata searchable in a Sheet — like Spotlight for Drive.
Updated Dec 7, 2025
Build a shared-folder onboarding kit
Auto-grant new Northwind hires the folders they need on day one.
Updated Nov 29, 2025
Route saved email attachments to project folders
File Gmail attachments into the right Northwind client folder based on subject keywords.
Updated Nov 25, 2025
Bundle a folder of images into one PDF
Combine Northwind scans into a single deliverable PDF using a generation service.
Updated Nov 17, 2025