Auto-categorize a photo library
Tag Northwind Drive images by visual content — product, team, event, behind-the-scenes.
Published Feb 3, 2026
Northwind’s shared Drive holds thousands of photos with names like
IMG_4821.jpg. When the marketing team needs a product shot or a team
picture, they scroll. The pictures are perfectly useful — they just are not
findable, because the filename says nothing about what is in the frame.
This automation walks a Drive folder of images, sends each one to Claude’s vision model, and asks for a single category label — product, team, event, workspace, or other. Every result lands in an index sheet alongside the file name and a link, so a search of that sheet replaces an afternoon of scrolling.
What you’ll need
- A Drive folder of images to classify — the script processes anything with
an
image/MIME type and skips everything else. - A Google Sheet to act as the index. The script appends rows to its first
tab, so add a header row (
File name,Category,Link) yourself first. - An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely. - The folder ID is passed in as an argument; the index sheet ID goes in the config block at the top.
The script
// The sheet that collects the file name, category, and link for each image.
const PHOTO_INDEX_ID = '1abcPhotoIndexId';
// The fixed set of labels Claude must choose from.
const CATEGORIES = ['product', 'team', 'event', 'workspace', 'other'];
// Vision model used for classification — Haiku is fast and cheap enough.
const VISION_MODEL = 'claude-haiku-4-5-20251001';
/**
* Walks a Drive folder and classifies every image inside it, appending one
* row per image to the index sheet.
*
* @param {string} folderId ID of the Drive folder to scan.
*/
function categorisePhotos(folderId) {
const files = DriveApp.getFolderById(folderId).getFiles();
const sheet = SpreadsheetApp.openById(PHOTO_INDEX_ID).getSheets()[0];
let count = 0;
while (files.hasNext()) {
const f = files.next();
// 1. Skip anything that is not an image.
if (!f.getMimeType().startsWith('image/')) continue;
// 2. Base64-encode the image bytes for the API payload.
const b64 = Utilities.base64Encode(f.getBlob().getBytes());
// 3. Ask Claude for a single category label.
const cat = visionClassify(b64, f.getMimeType());
// 4. Record the result in the index sheet.
sheet.appendRow([f.getName(), cat, f.getUrl()]);
count++;
}
Logger.log('Categorised ' + count + ' images.');
}
/**
* Sends one image to Claude's vision model and returns its category label.
*
* @param {string} b64 Base64-encoded image data.
* @param {string} mime MIME type of the image (e.g. "image/jpeg").
* @return {string} One of CATEGORIES.
*/
function visionClassify(b64, mime) {
// The key lives in Script Properties — never pasted into the code.
const key = PropertiesService.getScriptProperties()
.getProperty('ANTHROPIC_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model: VISION_MODEL,
max_tokens: 20,
messages: [{
role: 'user',
content: [
{ type: 'image', source: { type: 'base64', media_type: mime, data: b64 } },
{ type: 'text', text: 'Pick one: ' + CATEGORIES.join(', ') +
'. Return only the label.' },
],
}],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
categorisePhotosopens the given folder and grabs an iterator over its files, plus the first tab of the index sheet.- For each file it checks the MIME type and skips anything that is not an image — documents, videos, and sub-folders are ignored.
- It reads the image bytes and base64-encodes them, the format the Anthropic API expects for inline images.
- It calls
visionClassify, which sends the encoded image and a short instruction pinning the answer to one of the fiveCATEGORIES. max_tokensis set to 20 — the reply is a single word, so there is no reason to allow more.- The file name, returned category, and Drive link are appended as a row to the index sheet.
Example run
A folder contains four images. After a run, the index sheet reads:
| File name | Category | Link |
|---|---|---|
| IMG_4821.jpg | product | https://drive.google.com/… |
| IMG_4822.jpg | team | https://drive.google.com/… |
| DSC_0190.jpg | event | https://drive.google.com/… |
| screenshot.png | workspace | https://drive.google.com/… |
Now anyone can filter the sheet to category = product and get every product
shot in seconds, with a link straight to the file.
Run it
This is an on-demand job — run it when a batch of new photos arrives:
- The function takes a
folderIdargument, so call it from a small wrapper rather than runningcategorisePhotosdirectly:
function runPhotoCategorisation() {
categorisePhotos('1abcPhotoFolderId');
}
- In the Apps Script editor, select
runPhotoCategorisationand click Run. - Approve the authorisation prompt the first time, then open the index sheet to see the results.
Watch out for
- Every run re-processes the whole folder and appends fresh rows — there is no deduplication. Run it on a folder of new uploads, or move processed images into an archive folder afterwards.
- Each image is a separate API call. A folder of hundreds of photos costs hundreds of calls and can hit the six-minute execution limit — split large folders into batches.
- Large images make large base64 payloads. Very high-resolution photos can push the request size up and slow each call; resize before classifying if the folder is full of full-resolution originals.
- The model returns free text. It is told to return only a label, but if it
ever replies with a stray sentence that text lands in the sheet as-is —
add a check that the result is in
CATEGORIESif you need it strict. - Vision classification is a judgement, not a fact. Ambiguous shots (a product on a desk, a team at an event) may land in a category you would not have picked. Spot-check the index before relying on it.
Related
Build an AI keyword-clustering tool
Group Northwind's tracked search terms into topic clusters — for SEO content planning.
Updated Feb 19, 2026
Build an AI customer-churn predictor
Flag at-risk Northwind accounts from behavioural signals — usage, support tickets, billing.
Updated Feb 15, 2026
Build a context-aware AI data validator
Catch values that look wrong in context — '£10' for a Northwind retainer is suspicious.
Updated Feb 7, 2026
Build an AI bug-triage system
Categorise and prioritise Northwind's reported issues automatically — type, severity, owner.
Updated Jan 22, 2026
Build a document-classification system
Sort Northwind Drive files into types by their content — contracts, briefs, invoices.
Updated Jan 6, 2026