Working with multiple sheets
How to query different tabs within the same spreadsheet, combine data from multiple sheets, and handle cross-sheet lookups with SheetsAPI.
A Google Spreadsheet can contain many tabs (sheets). SheetsAPI exposes each tab as a separate endpoint, using the tab name as the sheetName path segment.
Querying different tabs
Each tab gets its own URL:
# Products tab
GET /api/spreadsheets/YOUR_USER_KEY/Products
# Orders tab
GET /api/spreadsheets/YOUR_USER_KEY/Orders
# Customers tab
GET /api/spreadsheets/YOUR_USER_KEY/CustomersThe sheetName is case-sensitive and must match the tab name exactly. Use URL encoding for tab names with spaces:
GET /api/spreadsheets/YOUR_USER_KEY/Product%20CatalogFetching multiple sheets in parallel
If your application needs data from several tabs at once, fire all requests concurrently:
const KEY = process.env.SHEETSAPI_KEY!;
const [productsRes, ordersRes, customersRes] = await Promise.all([
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Products?limit=200`),
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Orders?limit=200`),
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Customers?limit=200`),
]);
const [products, orders, customers] = await Promise.all([
productsRes.json(),
ordersRes.json(),
customersRes.json(),
]);Three parallel fetches complete in the time of the slowest single request - not the sum of all three.
Cross-sheet lookups in application code
SheetsAPI does not perform SQL-style JOINs - it returns rows from one sheet per request. Cross-sheet lookups are done in application code after fetching both sheets.
Example: attach order data to customer records
// Fetch both sheets
const [custRes, ordRes] = await Promise.all([
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Customers?limit=500`),
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Orders?limit=500`),
]);
type Customer = { ID: string; Name: string; Email: string };
type Order = { CustomerID: string; Amount: string; Date: string };
const { data: customers } = (await custRes.json()) as { data: Customer[] };
const { data: orders } = (await ordRes.json()) as { data: Order[] };
// Build a lookup map for O(1) access
const ordersByCustomer = new Map<string, Order[]>();
for (const order of orders) {
const existing = ordersByCustomer.get(order.CustomerID) ?? [];
existing.push(order);
ordersByCustomer.set(order.CustomerID, existing);
}
// Merge
const enriched = customers.map((c) => ({
...c,
orders: ordersByCustomer.get(c.ID) ?? [],
}));Caching multi-sheet data
When fetching several sheets for a single page render, cache the combined result rather than each sheet independently. This avoids multiple round-trips on subsequent requests:
let cache: { data: unknown; expiresAt: number } | null = null;
async function getAllSheets() {
if (cache && cache.expiresAt > Date.now()) return cache.data;
const [products, orders] = await Promise.all([
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Products?limit=200`).then((r) =>
r.json(),
),
fetch(`https://api.sheetsapi.io/api/spreadsheets/${KEY}/Orders?limit=200`).then((r) =>
r.json(),
),
]);
const data = { products: products.data, orders: orders.data };
cache = { data, expiresAt: Date.now() + 60_000 }; // 60 s TTL
return data;
}Listing available tabs
SheetsAPI does not expose a "list all tabs" endpoint - the sheet structure is determined by the spreadsheet owner. Maintain a list of tab names in your application configuration, or expose them as an environment variable:
# .env
SHEETSAPI_KEY=sk_YOUR_USER_KEY
SHEETSAPI_SHEETS=Products,Orders,Customers,Inventoryconst sheets = process.env.SHEETSAPI_SHEETS?.split(",") ?? [];Tab naming best practices
| Recommendation | Reason |
|---|---|
| Use PascalCase tab names | Products, Orders - clean URLs, no URL encoding needed |
| Avoid spaces | Spaces require %20 in URLs - harder to read and type |
| Keep names stable | Renaming a tab breaks all API calls referencing the old name |
| Use a single sheet per data type | One tab = one entity; avoid mixing unrelated data |
Filtering across sheets
To filter data from multiple sheets server-side, apply the search param independently on each request:
const category = "Electronics";
const [products, inventory] = await Promise.all([
fetch(`/api/spreadsheets/${KEY}/Products?search=Category:${category}`),
fetch(`/api/spreadsheets/${KEY}/Inventory?search=Category:${category}`),
]).then((rs) => Promise.all(rs.map((r) => r.json())));Each request returns only matching rows, keeping payloads small even for large sheets.