Yesterday append_row wrote a new KPI row to a specific Sheet you already had open. But what if you need to find the Sheet first? You have forty spreadsheets in Drive and you can't remember if the Q1 revenue tracker is called "Q1 Revenue" or "Revenue Q1" or "Quarterly Numbers."
After append_row I kept the spreadsheet_id in a variable. But in a real workflow I'd have to go to Drive, search, copy the ID, paste it in. That's three steps before I can even start.
Exactly. GOOGLESHEETS_SEARCH_SPREADSHEETS collapses those three steps into one call. Pass a keyword string — it searches names and metadata across every Sheet your account can see and returns a list of file dicts.
result = toolset.execute_action(Action.GOOGLESHEETS_SEARCH_SPREADSHEETS, {"query": query})
files = result.get("files", [])The key is files? I would have guessed spreadsheets or results. Is that a Google naming thing or a Composio thing?
Google Drive's API groups all file types under files — Sheets, Docs, Slides, everything. Composio surfaces that directly. Once you know the shape you never have to guess again: result.get("files", []) is the pattern for any Drive search action.
So a row in Sheets was a list of strings, and a Drive search is a list of dicts. Everything in this track really is just dicts and lists all the way down.
Every app, every action. Dicts in, dicts out, lists when there are multiples. The whole Google Workspace is just nested JSON if you squint hard enough.
I can already see a workflow: search for the Sheet, grab the first match's id, pass it straight to append_row. Like this:
matches = search_sheets("KPI Tracker")
if matches:
append_row(matches[0]["id"], "A1", ["2026-Q2", 14200, "on track"])That's it — dynamic ID resolution. No hard-coded IDs, no browser trip. The search gives you the ID; the append uses it. Your scripts work even when the Sheet gets renamed. Next up: Docs. GOOGLEDOCS_GET_DOCUMENT_BY_ID reads a full document — and once you can read a Doc programmatically, turning it into a digest email becomes one more chain.
GOOGLESHEETS_SEARCH_SPREADSHEETS queries every Google Sheet your account can access and returns matching file metadata as a list.
| Key | Value |
|---|---|
files | List of file dicts (name, id, mimeType, etc.) |
Use result.get("files", []) — an empty list is valid when no Sheet matches the query.
Combine search_sheets with read_range or append_row to avoid hard-coded spreadsheet IDs:
matches = search_sheets("KPI Tracker")
if matches:
sheet_id = matches[0]["id"]
data = read_range(sheet_id, "A1:D10")This pattern makes scripts portable — the same code works across accounts and renamed files.
Yesterday append_row wrote a new KPI row to a specific Sheet you already had open. But what if you need to find the Sheet first? You have forty spreadsheets in Drive and you can't remember if the Q1 revenue tracker is called "Q1 Revenue" or "Revenue Q1" or "Quarterly Numbers."
After append_row I kept the spreadsheet_id in a variable. But in a real workflow I'd have to go to Drive, search, copy the ID, paste it in. That's three steps before I can even start.
Exactly. GOOGLESHEETS_SEARCH_SPREADSHEETS collapses those three steps into one call. Pass a keyword string — it searches names and metadata across every Sheet your account can see and returns a list of file dicts.
result = toolset.execute_action(Action.GOOGLESHEETS_SEARCH_SPREADSHEETS, {"query": query})
files = result.get("files", [])The key is files? I would have guessed spreadsheets or results. Is that a Google naming thing or a Composio thing?
Google Drive's API groups all file types under files — Sheets, Docs, Slides, everything. Composio surfaces that directly. Once you know the shape you never have to guess again: result.get("files", []) is the pattern for any Drive search action.
So a row in Sheets was a list of strings, and a Drive search is a list of dicts. Everything in this track really is just dicts and lists all the way down.
Every app, every action. Dicts in, dicts out, lists when there are multiples. The whole Google Workspace is just nested JSON if you squint hard enough.
I can already see a workflow: search for the Sheet, grab the first match's id, pass it straight to append_row. Like this:
matches = search_sheets("KPI Tracker")
if matches:
append_row(matches[0]["id"], "A1", ["2026-Q2", 14200, "on track"])That's it — dynamic ID resolution. No hard-coded IDs, no browser trip. The search gives you the ID; the append uses it. Your scripts work even when the Sheet gets renamed. Next up: Docs. GOOGLEDOCS_GET_DOCUMENT_BY_ID reads a full document — and once you can read a Doc programmatically, turning it into a digest email becomes one more chain.
GOOGLESHEETS_SEARCH_SPREADSHEETS queries every Google Sheet your account can access and returns matching file metadata as a list.
| Key | Value |
|---|---|
files | List of file dicts (name, id, mimeType, etc.) |
Use result.get("files", []) — an empty list is valid when no Sheet matches the query.
Combine search_sheets with read_range or append_row to avoid hard-coded spreadsheet IDs:
matches = search_sheets("KPI Tracker")
if matches:
sheet_id = matches[0]["id"]
data = read_range(sheet_id, "A1:D10")This pattern makes scripts portable — the same code works across accounts and renamed files.
Create a free account to get started. Paid plans unlock all tracks.