How to Scan Hundreds of Legal Files in Under an Hour
From Chaos to Case File
What's inside
- What you need before you start
- The complete copy-paste script
- Step-by-step setup guide
- What the results look like
- How to use your new inventory
If you are in the middle of a legal situation — a housing dispute, a complaint against an official, a landlord nightmare, anything involving paperwork — there is a good chance your evidence looks something like this:
- Screenshots sitting in your phone camera roll
- PDFs buried in your Downloads folder
- Scanned letters saved under names like IMG_3847.jpg
- Important emails you forwarded to yourself and never filed
This guide will walk you through a process that fixes all of that. By the end, you will have a clean, organized spreadsheet that lists every file, describes what is in it, pulls out dates and names, and gives each document a proper title.
The kind of thing a paralegal would charge hundreds of dollars to build. You are going to do it for almost nothing.
Good news: you probably already have most of this.
This whole process runs inside Google Drive and Google Sheets. If you have Gmail, you already have everything. If not, go to google.com and create a free account.
This is the single most important thing to do before running anything. All of your legal files need to be in one Google Drive folder.
You do not need to rename or sort anything yet. That is exactly what the AI is going to do for you.
This is the one part that sounds technical but is actually very simple. An API key is just a password that lets the script talk to an AI service on your behalf.
When the script runs, it opens each of your files, sends it to the AI, and gets back a description. The API key is how the AI knows the request is coming from you.
Here are your main options. All three work with this guide:
- Claude by Anthropic — anthropic.com/api
- OpenAI (same company as ChatGPT) — platform.openai.com
- Google Gemini (has a generous free tier) — aistudio.google.com
For each service: create an account, find the API section, and click Create API Key. Copy that key somewhere safe. You will paste it into the script in a moment.
Go to sheets.google.com and open a new blank sheet. Give it a name like "Case File Inventory." Leave it open. The script will find it automatically when it runs.
Below is the complete script. Copy everything in the code block and paste it into your Google Sheet. Step-by-step instructions on how to do that follow right after.
// ============================================================
// 🔑 ====== PUT YOUR API KEY RIGHT HERE 👇 ==================
const ANTHROPIC_API_KEY = "YOUR_ANTHROPIC_API_KEY_HERE";
// 🔑 ====== THAT'S THE ONLY THING YOU NEED TO CONFIGURE ====
// ============================================================
// ============================================================
// ⚙️ CONFIG — Change these if needed
// ============================================================
const DRIVE_FOLDER_ID = "YOUR_FOLDER_ID_HERE"; // 👈 Your source folder
const SHEET_NAME = "File Inventory"; // 👈 Tab name in the Sheet
const CLAUDE_MODEL = "claude-opus-4-5"; // 👈 Model to use
// MIME types to EXCLUDE (Google Docs, Sheets, Docs editors, plain text, Word)
const EXCLUDED_MIME_TYPES = [
"application/vnd.google-apps.document",
"application/vnd.google-apps.spreadsheet",
"application/vnd.google-apps.presentation",
"application/vnd.google-apps.form",
"application/vnd.google-apps.drawing",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/msword",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel",
"text/plain",
"text/csv",
];
// ============================================================
/**
* STEP 1: Run this first — lists all qualifying files into the sheet
*/
function listFilesFromDrive() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
} else {
sheet.clearContents();
}
const headers = [
"File ID",
"File Name",
"MIME Type",
"File Size (bytes)",
"Drive Link",
"Generated Title",
"Description",
"Start Date",
"End Date",
"People Involved",
"AI Status"
];
sheet.appendRow(headers);
sheet.getRange(1, 1, 1, headers.length).setFontWeight("bold").setBackground("#1a1a2e").setFontColor("#ffffff");
sheet.setFrozenRows(1);
const folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
const files = folder.getFiles();
let count = 0;
while (files.hasNext()) {
const file = files.next();
const mimeType = file.getMimeType();
if (EXCLUDED_MIME_TYPES.includes(mimeType)) continue;
const row = [
file.getId(),
file.getName(),
mimeType,
file.getSize(),
file.getUrl(),
"", // Generated Title
"", // Description
"", // Start Date
"", // End Date
"", // People Involved
"PENDING"
];
sheet.appendRow(row);
count++;
}
sheet.autoResizeColumns(1, headers.length);
SpreadsheetApp.getUi().alert(`✅ Done! Found ${count} qualifying files. Now run "Scan Files with AI" to get descriptions.`);
}
/**
* STEP 2: Run this after Step 1 — calls Claude API for each PENDING row
* Safe to re-run — only processes rows with status "PENDING"
*/
function scanFilesWithAI() {
if (!ANTHROPIC_API_KEY || ANTHROPIC_API_KEY === "YOUR_ANTHROPIC_API_KEY_HERE") {
SpreadsheetApp.getUi().alert("❌ Please add your Anthropic API key at the top of the script first!");
return;
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
SpreadsheetApp.getUi().alert("❌ No sheet found. Run 'List Files from Drive' first.");
return;
}
const data = sheet.getDataRange().getValues();
let processedCount = 0;
let errorCount = 0;
for (let i = 1; i < data.length; i++) {
const row = data[i];
const status = row[10];
if (status !== "PENDING") continue;
const fileId = row[0];
const fileName = row[1];
const mimeType = row[2];
const sheetRow = i + 1;
try {
Logger.log(`Processing row ${sheetRow}: ${fileName}`);
sheet.getRange(sheetRow, 11).setValue("PROCESSING...");
SpreadsheetApp.flush();
const aiResult = callClaudeForFile(fileId, fileName, mimeType);
sheet.getRange(sheetRow, 6).setValue(aiResult.title);
sheet.getRange(sheetRow, 7).setValue(aiResult.description);
sheet.getRange(sheetRow, 8).setValue(aiResult.startDate);
sheet.getRange(sheetRow, 9).setValue(aiResult.endDate);
sheet.getRange(sheetRow, 10).setValue(aiResult.people);
sheet.getRange(sheetRow, 11).setValue("✅ DONE");
processedCount++;
Utilities.sleep(1000);
} catch (e) {
Logger.log(`Error on row ${sheetRow}: ${e.message}`);
sheet.getRange(sheetRow, 11).setValue("❌ ERROR: " + e.message.substring(0, 80));
errorCount++;
}
SpreadsheetApp.flush();
}
SpreadsheetApp.getUi().alert(`✅ AI scan complete!\nProcessed: ${processedCount}\nErrors: ${errorCount}`);
}
/**
* Fetches a file from Drive and sends it to Claude for analysis.
* Returns an object: { title, description, startDate, endDate, people }
*/
function callClaudeForFile(fileId, fileName, mimeType) {
const file = DriveApp.getFileById(fileId);
const blob = file.getBlob();
const base64Data = Utilities.base64Encode(blob.getBytes());
let contentArray = [];
const isPdf = mimeType === "application/pdf";
const isImage = mimeType.startsWith("image/");
const systemPrompt = `You are a document analyst. The user will give you a file. Analyze it and respond ONLY with valid JSON — no markdown, no explanation, just raw JSON.
Return this exact structure:
{
"title": "YYYY-MM-DD Descriptive Title Including Key Parties",
"description": "2-3 sentence summary of what this document is about",
"startDate": "YYYY-MM-DD or 'Unknown'",
"endDate": "YYYY-MM-DD or 'Unknown'",
"people": "Comma-separated list of names mentioned, or 'None identified'"
}
Rules:
- Title format MUST start with the date like: "2025-08-14 Notice to Vacate — Landlord to Tenant"
- If only one date, use the same date for both startDate and endDate
- For dates: look inside the document content only. IGNORE any date embedded in a screenshot filename (e.g., "Screenshot 2025-01-01" is NOT the document date)
- If no date can be determined from the content itself, use "Unknown" for both date fields
- Keep description factual and concise`;
if (isPdf) {
contentArray = [
{ type: "document", source: { type: "base64", media_type: "application/pdf", data: base64Data } },
{ type: "text", text: `Analyze this file. Its filename is: "${fileName}"` }
];
} else if (isImage) {
contentArray = [
{ type: "image", source: { type: "base64", media_type: mimeType, data: base64Data } },
{ type: "text", text: `Analyze this file. Its filename is: "${fileName}"` }
];
} else {
contentArray = [
{ type: "text", text: `I have a file named "${fileName}" with MIME type "${mimeType}". I cannot extract its content directly, but please generate a best-guess title and description based on the filename alone. Follow the JSON structure exactly.` }
];
}
const payload = {
model: CLAUDE_MODEL,
max_tokens: 500,
system: systemPrompt,
messages: [{ role: "user", content: contentArray }]
};
const options = {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"anthropic-beta": "pdfs-2024-09-25"
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
if (responseCode !== 200) {
throw new Error(`API returned ${responseCode}: ${responseText.substring(0, 200)}`);
}
const parsed = JSON.parse(responseText);
const rawText = parsed.content[0].text.trim();
const cleanJson = rawText.replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/```\s*$/i, "").trim();
const result = JSON.parse(cleanJson);
return {
title: result.title || "Unknown",
description: result.description || "No description returned",
startDate: result.startDate || "Unknown",
endDate: result.endDate || "Unknown",
people: result.people || "None identified"
};
}
/**
* Adds a custom menu to the spreadsheet when opened
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("📁 Drive Scanner")
.addItem("Step 1 — List Files from Drive", "listFilesFromDrive")
.addSeparator()
.addItem("Step 2 — Scan Files with AI", "scanFilesWithAI")
.addToUi();
}
- In your Google Sheet, click Extensions in the top menu bar
- Click Apps Script — a new tab opens
- Delete everything you see in that tab (there will be a placeholder function already there)
- Paste the entire script from above into that empty space
- Press Ctrl+S on Windows or Cmd+S on Mac to save
Near the very top of the script you just pasted, you will see two things clearly marked with arrows:
- A line for your API key
- A line for your Google Drive folder ID
To find your folder ID: open your Drive folder in a browser. Look at the URL. The long string of letters and numbers at the very end (after the last slash) is your folder ID. Copy and paste that in.
- Click the Run button (the triangle play button) in Apps Script
- Google will ask you to authorize. Click Review Permissions
- Choose your Google account
- You may see a warning saying "Google has not verified this app" — that is normal for personal scripts
- Click Advanced, then click "Go to your script name" at the bottom
- Click Allow
This only happens once. It is just Google confirming you want the script to access your Drive and Sheet.
Go back to your Google Sheet and refresh the page. You should now see a new menu item in the top bar called Drive Scanner. Click it.
Click Step 1 — List Files from Drive. The script scans your folder and fills in one row per qualifying file. It automatically skips Google Docs, Word documents, spreadsheets, and plain text files. PDFs, images, and most other file types are included.
Click Step 2 — Scan Files with AI. This is where it all happens. The script goes row by row, opens each file, sends it to the AI, and waits for a response.
When the scan is done, every row in your sheet will have all of this filled in:
- Generated Title — starts with the date, like "2025-08-14 Notice to Vacate from Landlord"
- Description — 2 to 3 plain-English sentences about what the document says or shows
- Start Date and End Date — pulled from the document content, not the filename
- People Involved — any names the AI spotted in the document
- Status — whether the scan succeeded or had an error
Here is an example of what a few rows might look like after the scan completes:
⚠️ All names, dates, and details in this table are completely fictional and used for illustration purposes only.
| Generated Title | Description | Start Date | People Involved | Status |
|---|---|---|---|---|
| 2025-03-02 Notice to Vacate — Property Manager to Tenant | A formal written notice issued by the property manager demanding the tenant vacate the premises within 30 days. The notice cites lease violations including unauthorized subletting. Signed by the property manager and addressed to the tenant of record. | 2025-03-02 | R. Holloway (manager), D. Chen (tenant) | ✅ DONE |
| 2025-03-18 Police Incident Report — Trespassing Complaint | An official incident report filed with local police documenting a trespassing complaint. The reporting officer notes that the suspect was observed on the property after a no-contact order had been issued. Report includes badge numbers and case reference. | 2025-03-18 | Officer T. Reyes, J. Morales (complainant) | ✅ DONE |
| 2025-04-10 Text Message Screenshots — Harassment Documentation | A series of screenshots showing text messages sent between April 8 and April 10, 2025. The messages contain repeated unwanted contact and threatening language from the sender. Screenshots include timestamps and phone numbers. | 2025-04-08 | None identified (numbers only) | ✅ DONE |
| Unknown — Handwritten Letter re: Rent Dispute | A handwritten letter disputing a claimed rent overpayment from the prior year. The author references a specific payment made in cash and requests a receipt or correction to the ledger. No date is written on the letter itself. | Unknown | Author name illegible | ✅ DONE |
Sort the sheet by Start Date. You now have a chronological view of your entire case. Gaps in the timeline are just as important as the documents — they show you what might be missing.
Scroll through the descriptions. Look for documents with specific dates, named parties, and concrete actions or statements. Those are the ones an attorney or advocate will want to see first.
You can share the Google Sheet directly with anyone — an attorney, an advocate, a family member helping you. You can also download it as a PDF or Excel file. Handing someone a clean, organized inventory shows that you are serious and that you know what you have.
Every time a new file comes in, drop it in your Drive folder and run Step 2 again. The script skips everything already scanned and only processes new additions.
It happens sometimes. Just correct it manually in the sheet. The AI's output is a starting point, not a final answer.
Some unusual file types cannot be sent to the AI. The Status column will say ERROR for those rows. You can review them manually or skip them.
Your files are sent to whichever AI service you chose. Those companies have privacy policies that govern how they handle your data. API usage is generally not used to train their models, but always check the current policy. Avoid scanning documents with Social Security numbers, bank account numbers, or passwords.
No. You are copying and pasting. The hardest part is finding your folder ID in the Drive URL, and even that takes about ten seconds.
Every screenshot, every scanned letter, every PDF you saved — you kept them because they mattered. This process makes sure they actually get used.
In an hour or less, you go from a chaotic folder of nameless files to a clean, searchable, shareable inventory of your entire case.