Sync Airtable to Google Sheets with the REST API, CSV export, or a scheduled connector, including the attachment URLs that expire and the pagination that stops at 100 records.
Airtable has no first-party Google Sheets sync. You can export a view to CSV in about two minutes, and that is genuinely the right answer for a one-off. Anything that needs to stay current means going through the REST API, either yourself with Apps Script or through a connector. This guide covers every route, and the three details that break most first attempts: pagination that stops at 100 records, personal access tokens replacing the old API keys, and attachment URLs that expire.
The two-minute option: CSV export
In your base, open the view you want, click the view menu and choose Download CSV. Import the file into Sheets via File → Import. Worth knowing: the export follows the view, not the table, so any filters, hidden fields and sort order in that view are applied to the file. That is useful when you want a subset, and surprising when you expected the whole table and got a filtered slice.
Honest take: if you need this once, or once a quarter, stop here. Everything below exists to avoid repeating this by hand.
Airtable to Google Sheets for free (Apps Script + REST API)
First, authentication. If you find an older tutorial telling you to grab an API key from your account page, it is out of date: Airtable retired user API keys and they stopped working in early 2024. Create a personal access token from your account's Builder Hub, give it the data.records:read scope (add data.records:write if you plan to push changes back), and grant it access to the specific base you want. Tokens are scoped per base, so one token does not automatically read all of them.
You also need the base ID and table name. Open Help → API documentation from inside the base; the base ID is the identifier beginning app shown throughout those docs, and it also appears in the URL when the base is open.
The detail that catches nearly everyone: the API returns a maximum of 100 records per request. When more exist, the response carries an offset value, and you request the next page by passing it back. Code that reads a single response works perfectly on a test table and silently truncates the real one:
function importAirtableTable() { const TOKEN = "YOUR_PERSONAL_ACCESS_TOKEN"; const BASE_ID = "appXXXXXXXXXXXXXX"; const TABLE = "Projects"; // table name or table ID const VIEW = "Grid view"; // optional: respects the view's filters const headers = { Authorization: "Bearer " + TOKEN }; const base = "https://api.airtable.com/v0/" + BASE_ID + "/" + encodeURIComponent(TABLE); // Page through every record. 100 per response is the maximum, so any // table larger than that needs this loop, not a single fetch. const records = []; let offset = null; do { let url = base + "?pageSize=100&view=" + encodeURIComponent(VIEW); if (offset) url += "&offset=" + offset; const page = JSON.parse( UrlFetchApp.fetch(url, { headers }).getContentText() ); records.push(...page.records); offset = page.offset; // absent once the last page is reached if (offset) Utilities.sleep(250); // stay under 5 requests/second } while (offset); if (!records.length) return; // Airtable omits empty fields entirely, so collect every key that appears // across all records rather than trusting the first one to be complete. const columns = []; records.forEach((r) => Object.keys(r.fields).forEach((k) => { if (columns.indexOf(k) === -1) columns.push(k); }) ); const rows = records.map((r) => columns.map((c) => { const v = r.fields[c]; if (v === undefined || v === null) return ""; // Multi-selects, linked records and collaborators arrive as arrays. if (Array.isArray(v)) { return v.map((x) => (x && x.url) ? x.url : (x && x.name) ? x.name : x).join(", "); } if (typeof v === "object") return v.name || JSON.stringify(v); return v; }) ); const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName(TABLE) || ss.insertSheet(TABLE); sheet.clearContents(); sheet.getRange(1, 1, 1, columns.length).setValues([columns]); sheet.getRange(2, 1, rows.length, columns.length).setValues(rows);}Run it once to authorise, then add a time-driven trigger under Triggers → Add Trigger → Time-driven. Two behaviours worth calling out in that code. Airtable omits empty fields from a record entirely rather than returning null, so building headers from the first record alone loses any column that happened to be blank in row one. And several field types arrive as arrays or objects rather than strings: multi-selects, linked records, collaborators and attachments all need flattening before they can go into a cell.
Limits: the API allows about 5 requests per second per base, and Apps Script caps execution at six minutes, so very large bases need the import split across runs. There is no write-back here unless you build it, and the script needs updating whenever the schema changes.
Automation tools and dedicated sync products
Zapier, Make and n8n all have Airtable triggers that fire when a record is created or updated, plus Google Sheets actions to append or update a row. They suit event-driven work: a new record should also become a sheet row, a Slack message and a CRM task.
They are a poor fit for reporting. Rebuilding a 5,000-row table through per-record automations is slow and expensive, and Zapier's free tier of 100 tasks a month disappears immediately at that scale. If what you want is a continuously mirrored copy rather than a report, dedicated two-way products such as Whalesync are built for exactly that and start around $29 a month.
The Brooked method
Brooked handles the pagination, the missing-field problem and the array flattening described above, so a table lands as a clean rectangle whatever its field types. Several tables from the same base can import into one spreadsheet on a single schedule, and write-back is supported for editable fields.
Step 1: Install Brooked
Install Brooked from the Google Workspace Marketplace. Free tier includes 100 imports per month, no credit card required.
Step 2: Connect Airtable
Open Brooked in your sheet, choose Airtable, and paste a personal access token scoped to the bases you want. Brooked lists the bases and tables that token can reach; pick a table, optionally pick a view to inherit its filters, and import.
Step 3: Schedule the refresh
Set the import to re-run hourly, daily or weekly. Because it re-reads the table rather than listening for individual record events, a scheduled refresh also catches changes made while an event-based automation was failing, which is a quiet failure mode of webhook-driven setups.
Method comparison
| Method | Cost | Setup | Refresh | Two-way | Best for |
|---|---|---|---|---|---|
| CSV export | Free | 2 min | Manual, per view | No | A one-off snapshot of a single view |
| Apps Script + Airtable REST API | Free | 20 to 30 min | Time-driven trigger | DIY | Custom field mapping, no budget |
| Zapier / Make / n8n | Free tier limited · paid for volume | 10 to 20 min | On record change | DIY (second workflow) | Reacting to individual record changes |
| Two-way sync tools (Whalesync and similar) | Paid, from ~$29/mo | 10 to 20 min | Near real-time | Yes | Keeping a base and a sheet mirrored continuously |
| Brooked | Free tier · Pro $29/user/mo | ~3 min | Scheduled (hourly, daily, weekly) | Yes | Scheduled reporting, several tables in one sheet |
Common use cases
- Project reporting: pull a projects table into Sheets for status roll-ups leadership already reads there
- Budget tracking: combine Airtable project records with finance data that lives in the spreadsheet
- Content calendars: report on an editorial base without giving every stakeholder a base seat
- Inventory and ops: schedule a daily snapshot so you have history Airtable itself does not retain
- Cross-base reporting: bring several bases into one sheet to report across them
Troubleshooting common issues
Related Articles
- PostgreSQL to Google Sheets: Connect & Auto-Sync in 10 Minutes (2026)
- How to Connect MySQL to Google Sheets
- How to Connect Supabase to Google Sheets
Frequently asked questions
Does Airtable have a native Google Sheets integration?
Not a built-in sync. Airtable offers CSV export from any view, and its Automations can be pointed at Google Sheets via a scripting step, but there is no first-party connector that keeps a sheet continuously up to date. Continuous sync means the REST API, an automation tool, a dedicated sync product, or a Sheets add-on.
Why does my Airtable import stop at 100 records?
100 is the maximum page size on the Airtable REST API, not a cap on your data. Each response includes an offset value when more records exist; pass it back as the offset query parameter to fetch the next page, and keep looping until the response no longer contains one. Code that reads only the first response will silently truncate every table over 100 rows.
Can I sync Airtable to Google Sheets for free?
Yes. CSV export is free and takes two minutes for a one-off. For something that refreshes on its own, the Apps Script method in this guide calls the Airtable REST API with a free personal access token and runs on a free time-driven trigger. Brooked's free tier also covers 100 imports a month if you would rather not maintain a script.
Do Airtable API keys still work?
No. Airtable deprecated user API keys and they stopped working in early 2024. Create a personal access token instead, from your account's Builder Hub under Personal access tokens, and grant it the data.records:read scope plus access to the specific bases you want to read. Tokens are scoped per base, so a token created for one base returns 403 on another.
Why are my attachment links broken after importing?
Attachment URLs returned by the Airtable API are temporary. They expire a couple of hours after being generated, so a link written into a spreadsheet works when you test it and returns an error days later. If you need durable links, store the file elsewhere and keep a permanent URL in a text field, or re-import shortly before the links are needed.
Can I write changes from Google Sheets back into Airtable?
Yes, but note what is writable. The Airtable API accepts updates to normal fields, and Brooked supports write-back on that basis. Computed fields cannot be written to from anywhere: formula, rollup, lookup, count, autonumber and created/modified time fields are derived by Airtable and will reject writes. Change the underlying field instead.
Connect Airtable to Google Sheets today.
Free tier: 100 imports per month, no credit card required. Or see the full Airtable integration details.
Install Brooked free →

