Connect FlutterFlow to Airtable using a FlutterFlow API Call (API Group) pointed at Airtable's REST API with a Bearer Personal Access Token. Create an API Group with base URL `https://api.airtable.com/v0/{baseId}`, add a GET endpoint for your table, parse the `records[].fields` response with JSON Paths, and bind the results to a ListView. Route any write calls through a Firebase Cloud Function to keep the PAT off users' devices.
| Fact | Value |
|---|---|
| Tool | Airtable |
| Category | Database & Backend |
| Method | FlutterFlow API Call |
| Difficulty | Intermediate |
| Time required | 45 minutes |
| Last updated | July 2026 |
Use Airtable as a No-SQL Backend for Your FlutterFlow App
Airtable sits at the intersection of a spreadsheet and a database. Non-technical founders love it because the data lives in a familiar grid, but it also has a clean REST API that returns JSON — which makes it a natural backend for FlutterFlow apps. You can use Airtable as a product catalog, a CRM, a job board, or any data store where you want your team to edit records in a friendly web UI without touching a database console.
The integration works via FlutterFlow's built-in API Calls panel. Every Airtable base gets a unique ID (visible in the URL or via the Airtable metadata API), and every table is reached at `https://api.airtable.com/v0/{baseId}/{tableName}`. You authenticate with a Personal Access Token sent as a `Bearer` header. GET requests come back as a JSON object with a `records` array; each record has an `id` and a `fields` object containing your column values.
Airtable's free tier allows up to 1,200 records per base, and paid plans start at roughly $20/user/month — check Airtable's current pricing for exact figures. The API enforces a rate limit of 5 requests per second per base, so be careful not to fire per-item API calls inside a list. Reads can use a dedicated read-only token, but any write token that creates or updates records must never live inside the compiled Flutter app — route those through a Firebase Cloud Function so the secret stays server-side and never ships to users' devices.
Integration method
FlutterFlow connects to Airtable by calling Airtable's REST API through a FlutterFlow API Group. You configure the base URL, attach a Bearer Personal Access Token in the header, define GET and POST endpoints for your tables, and parse the returned `records` array with JSON Paths. Because the PAT is a powerful secret, writes should be proxied through a Firebase Cloud Function rather than embedded in the client.
Prerequisites
- An Airtable account with at least one base and table containing data
- Your Airtable Base ID (find it in the URL: `airtable.com/{baseId}/{viewId}` or via api.airtable.com/v0/meta/bases)
- A FlutterFlow project (Starter plan or above supports API Calls)
- A Firebase project with Cloud Functions enabled (only needed for write operations)
- Basic familiarity with FlutterFlow's widget canvas and Action Flow Editor
Step-by-step guide
Create a scoped Personal Access Token in Airtable
Sign in to Airtable and open your account page at airtable.com/create/tokens. Click 'Create new token', give it a descriptive name like 'FlutterFlow Read Token', and under Scopes select 'data.records:read'. Under Access, choose 'Only the following bases' and select your base — this limits the blast radius if the token is ever exposed. Click 'Create token' and copy the value immediately; Airtable shows it only once. If you also need to create or update records, create a second, separate token with 'data.records:write' scope. Keep that write token exclusively for your Firebase Cloud Function — never paste it into FlutterFlow. Think of the read token as a library card and the write token as a master key: the library card can live in the app because it can only read; the master key stays locked in your server environment. Record your Base ID as well. You can find it in the Airtable URL when viewing your base — it starts with 'app' followed by alphanumeric characters, for example `appXXXXXXXXXXXXXX`. You'll need both the Base ID and the table name (exactly as it appears in Airtable, spaces and all) when configuring the API Group.
Pro tip: Create a read-only token for display calls and a separate write token that lives only in your Cloud Function. Never reuse the same token across both contexts.
Expected result: You have a read-only Personal Access Token copied to your clipboard and have noted your Base ID (starts with 'app...').
Build the Airtable API Group in FlutterFlow
Open your FlutterFlow project and click 'API Calls' in the left navigation panel. Click '+ Add' and choose 'Create API Group'. Name it 'Airtable', set the Base URL to `https://api.airtable.com/v0/{baseId}` — replace `{baseId}` with your actual Base ID right here in the URL so you don't have to pass it as a variable on every call. Scroll to the Headers section and add a header: Key = `Authorization`, Value = `Bearer YOUR_READ_TOKEN_HERE`. Paste the actual token value directly. While it's true that a read-only token scoped to a single base is lower-risk than a write token, treat it as a secret and make sure you understand that it will be compiled into the app bundle — anyone who decompiles the APK can extract it. For most read-only display use-cases with a tightly scoped token this is an acceptable trade-off, but you should rotate the token periodically. Now click '+ Add' again inside the API Group to add your first API Call. Name it 'ListRecords'. Set the Method to GET and the API URL to `/{tableName}` — replace `{tableName}` with your exact table name (URL-encode any spaces as `%20`, or better yet rename the table in Airtable to remove spaces). Switch to the Variables tab and add a variable named `maxRecords` with a default of `100` so you can control page size. Add `?maxRecords={{maxRecords}}` to the URL query string.
1{2 "method": "GET",3 "url": "https://api.airtable.com/v0/{baseId}/{tableName}?maxRecords=100",4 "headers": {5 "Authorization": "Bearer YOUR_READ_TOKEN"6 }7}Pro tip: Airtable table names are case-sensitive. If your table is named 'Products' and you call '/products', you'll get a 404. Double-check the exact name in Airtable.
Expected result: The API Group 'Airtable' with a GET 'ListRecords' call appears in your API Calls panel. You can see the Authorization header configured.
Test the call and generate JSON Paths
With the ListRecords call open, click the 'Response & Test' tab. Click 'Test API Call' — FlutterFlow will fire the actual GET request to Airtable and display the raw JSON response in the panel. You should see a structure like this: `{ "records": [ { "id": "recXXX", "createdTime": "2024-01-01T00:00:00.000Z", "fields": { "Name": "Example", "Price": 29.99 } } ] }`. If the response shows `{ "error": { "type": "NOT_FOUND" } }` check that your Base ID and table name are correct. If you see a 401, the token is missing or malformed — re-check the Authorization header. Once you have a successful response, click 'Add JSON Path' to extract the fields you want. For the record ID use `$.records[:].id`. For each column in your table, use `$.records[:].fields.{ColumnName}` — for example `$.records[:].fields.Name` and `$.records[:].fields.Price`. The `[:]` slice notation extracts the value from every record in the array, giving you a list. FlutterFlow will show a preview of extracted values on the right. Add as many paths as columns you need to display. Name each one descriptively, for example `recordId`, `productName`, `productPrice`.
Pro tip: If a field name contains spaces (e.g. 'Product Name'), the JSON Path becomes `$.records[:].fields.Product Name` — FlutterFlow handles the space in the path editor but it's cleaner to rename Airtable columns to camelCase.
Expected result: The test returns a 200 response with real data. JSON Paths for each column you need are listed in the Response & Test panel with preview values.
Map the response to a Data Type and bind to a ListView
Creating a reusable Data Type makes it easy to pass Airtable records between widgets. In the left navigation, go to 'Data Types' and click '+ Add Data Type'. Name it 'AirtableRecord'. Add fields matching the JSON Paths you just created — for example `recordId` (String), `productName` (String), `productPrice` (double). Now drag a ListView widget onto your canvas. Select the ListView and open the Backend Query section in the right panel. Click '+ Add Query', choose 'API Call', and select your 'Airtable → ListRecords' call. FlutterFlow will let you map the JSON Path variables to the Data Type fields you just created — match `recordId` to `$.records[:].id`, `productName` to `$.records[:].fields.Name`, and so on. Inside the ListView, add a ListTile or Card widget. Use the 'Set Variable' bindings to reference the current list item's fields — click the binding icon on the Title field of the ListTile, choose 'List Item', and select `productName`. Do the same for subtitle, price, or any other field. Run the app in Test Mode or on a connected device and your Airtable data should appear as a scrollable list. Airtable returns up to 100 records per request by default; if you have more than 100 records, see the pagination note in the troubleshooting section.
Pro tip: Add a loading indicator widget above the ListView and toggle its visibility based on the query loading state — this gives users feedback while the Airtable call completes.
Expected result: The ListView shows real data from your Airtable table. Each list item displays the bound field values (name, price, etc.).
Proxy write operations through a Firebase Cloud Function
Creating or updating Airtable records requires sending a POST or PATCH request with a write-scope PAT in the Authorization header. That write token must never be stored in FlutterFlow or compiled into the app. The solution is a Firebase Cloud Function that receives a simple JSON payload from FlutterFlow and makes the authenticated Airtable call server-side. In the Firebase console, create a new Cloud Function (Node.js 18 or 20). The function reads the incoming JSON body, validates it, and calls the Airtable API with your write token stored in a Firebase environment secret — never hardcoded. The function returns a 200 with the Airtable response or a 4xx/5xx on error. Back in FlutterFlow, create a new API Call inside your Airtable group named 'CreateRecord'. Set the Method to POST, and the URL to your deployed Cloud Function URL (e.g. `https://us-central1-yourproject.cloudfunctions.net/createAirtableRecord`). Add a JSON Body variable with keys matching what your function expects (`tableName`, `fields`). Trigger this call from an Action Flow on a form's submit button — the write PAT never touches the device. If you'd rather skip building the proxy yourself, RapidDev's team builds FlutterFlow integrations like this every week — free scoping call at rapidevelopers.com/contact.
1// Firebase Cloud Function (index.js)2const functions = require('firebase-functions');3const axios = require('axios');45exports.createAirtableRecord = functions.https.onRequest(async (req, res) => {6 // Set CORS headers7 res.set('Access-Control-Allow-Origin', '*');8 if (req.method === 'OPTIONS') {9 res.set('Access-Control-Allow-Methods', 'POST');10 res.set('Access-Control-Allow-Headers', 'Content-Type');11 return res.status(204).send('');12 }1314 const { tableName, fields } = req.body;15 const BASE_ID = functions.config().airtable.base_id;16 const WRITE_TOKEN = functions.config().airtable.write_token;1718 try {19 const response = await axios.post(20 `https://api.airtable.com/v0/${BASE_ID}/${encodeURIComponent(tableName)}`,21 { fields },22 { headers: { Authorization: `Bearer ${WRITE_TOKEN}`, 'Content-Type': 'application/json' } }23 );24 return res.status(200).json(response.data);25 } catch (err) {26 const status = err.response?.status || 500;27 return res.status(status).json({ error: err.message });28 }29});Pro tip: Store the write token with `firebase functions:config:set airtable.write_token=pat_xxx airtable.base_id=appXXX` — never hardcode it in the function source.
Expected result: Form submissions from FlutterFlow successfully create new Airtable records. The write token is stored only in Firebase environment config, not in the app.
Handle pagination and rate-limit caching
Airtable returns a maximum of 100 records per request. If your table has more, the response includes an `offset` string at the top level: `{ "records": [...], "offset": "itr..." }`. To fetch the next page, add `?offset={offset}` to your next request. FlutterFlow does not auto-paginate, so you'll need to manage this manually with App State. Add an App State variable called `airtableOffset` (String, nullable). After your ListRecords call completes, check if the response includes an `$.offset` JSON Path. If it does, store it in `airtableOffset` and show a 'Load more' button. The button triggers the ListRecords call again with the offset variable appended to the URL. Append the new records to your existing list in App State rather than replacing it. For rate-limit management, Airtable allows 5 requests per second per base. If you build a screen that fires multiple API calls simultaneously (for example, fetching detail data for each list item separately), you will hit 429 errors very quickly. The fix is to fetch all the data you need in a single list call and display it from that single response. Cache the full response in App State so navigating back to the list doesn't fire the API again. If a 429 does occur, Airtable locks out requests for 30 seconds — add a retry delay in your Action Flow rather than retrying immediately. Also watch for field names with emoji or special characters — these break JSON Path bindings silently. In the Airtable base settings, rename columns to plain ASCII strings before building the integration.
Pro tip: Add a JSON Path `$.offset` to your ListRecords call. If it returns a non-empty value, show the 'Load more' button; otherwise hide it.
Expected result: Your app can load beyond 100 records using the offset parameter, and repeated calls to the same data come from App State cache instead of new API requests.
Common use cases
Product catalog app that syncs with a shared Airtable base
A small e-commerce or wholesale founder builds a FlutterFlow app that displays products, prices, and inventory from an Airtable base that their team updates daily. Sales reps can browse the catalog on mobile without needing Airtable access themselves. New rows in Airtable appear in the app the next time the list refreshes.
Build a product catalog screen that reads Name, Price, and ImageURL from an Airtable table called Products and displays them in a card ListView. Refresh the list when the user pulls down.
Copy this prompt to try it in FlutterFlow
Job board app backed by an Airtable job-listing base
A recruiter manages job openings in an Airtable base with columns for Title, Company, Location, and Description. A FlutterFlow app lets candidates browse open roles, filter by location, and tap a card to see the full description. Because candidates only read data, a scoped read-only token is safe enough for the API Group.
Create a jobs list screen that fetches rows from an Airtable table named 'Open Roles'. Show Title and Company in a ListTile. Tapping a row opens a detail page showing the full Description field.
Copy this prompt to try it in FlutterFlow
Lead capture form that writes new contacts to Airtable
A marketing founder wants a FlutterFlow form that captures name, email, and a message field, then POSTs a new record to an Airtable contacts base. Because the write call needs the PAT, a Firebase Cloud Function receives the form payload from FlutterFlow and makes the authenticated Airtable API call server-side — the secret never touches the phone.
Add a 'Contact Us' form with Name, Email, and Message fields. On submit, call a Cloud Function endpoint that creates a new record in the Airtable 'Leads' table with those field values.
Copy this prompt to try it in FlutterFlow
Troubleshooting
401 Unauthorized — the API call returns {"error":{"type":"AUTHENTICATION_REQUIRED"}}
Cause: The Authorization header is missing, malformed, or the Personal Access Token has been revoked.
Solution: Open the API Group in FlutterFlow, go to the Headers section, and confirm the key is exactly 'Authorization' and the value starts with 'Bearer ' (with a space) followed by the token. Double-check the token is still valid in Airtable → account → Personal Access Tokens. If the token was regenerated, update it in every API Group that uses it.
404 Not Found — {"error":{"type":"NOT_FOUND"}} even though the table exists
Cause: The Base ID in the URL is wrong, or the table name doesn't match exactly (Airtable is case-sensitive and space-sensitive).
Solution: Copy your Base ID directly from the Airtable API documentation page for your base (airtable.com/api) — it begins with 'app'. Confirm the table name matches exactly including capitalisation. If the name contains spaces, URL-encode them as %20 in the endpoint, or rename the table in Airtable to remove spaces.
429 Too Many Requests — API calls fail intermittently when the list has many items
Cause: Airtable enforces a rate limit of 5 requests per second per base. Firing a separate API call per list item in a quick loop exceeds this.
Solution: Restructure your data loading to fetch all needed records in one API call rather than per-item calls. Cache the response in an App State list variable so re-visiting the screen does not re-trigger the API. If you must make multiple calls, add a 300ms Wait action between them in the Action Flow Editor.
Web publish works in Test Mode but all API calls fail after publishing to the web
Cause: Browsers enforce CORS (Cross-Origin Resource Sharing). In Test Mode, FlutterFlow uses its own proxy, so CORS is bypassed. After web publish, the Flutter web app calls Airtable's API directly from the browser, and Airtable's CORS headers may block the origin.
Solution: In FlutterFlow's API Group settings, enable the 'Enable API Proxying' toggle. This routes web-build API calls through FlutterFlow's own proxy server, bypassing the browser's CORS restriction. Alternatively, route all calls through a Firebase Cloud Function which is a server-to-server call unaffected by CORS.
Best practices
- Use a scoped read-only Personal Access Token for display calls — limit it to 'data.records:read' on exactly the bases the app needs, and store write tokens only in your Firebase Cloud Function config.
- Never store a write-scope PAT in FlutterFlow's App Constants or API Group headers — it will be compiled into the app bundle and extractable by anyone who decompiles the APK/IPA.
- Cache Airtable responses in an App State list variable so users don't wait for a fresh API call every time they revisit a screen, and to avoid burning into the 5 req/sec rate limit.
- Rename Airtable column headers to plain ASCII camelCase (no spaces, no emoji) before building the integration — special characters silently break JSON Path bindings in FlutterFlow.
- Always add a JSON Path for `$.offset` and implement a 'Load more' button if your table can exceed 100 records — Airtable never returns more than 100 in a single page.
- Test rate-limit behaviour in a staging base separate from your production base — a runaway test that fires 100 quick calls will lock out your entire base for 30 seconds.
- Add meaningful error-state widgets (empty state, error message) to your ListView for when the Airtable API call returns a non-200 status — users should see a friendly message, not a blank screen.
- Rotate your read token every few months even if it has minimal scope — good hygiene against token leaks in third-party analytics or crash-reporting tools that capture network headers.
Alternatives
Firebase is a native FlutterFlow integration with real-time Firestore queries and built-in auth — choose it over Airtable when you need live data sync, offline support, or complex querying beyond simple table reads.
PostgreSQL (via Supabase native integration) gives you relational data with joins and SQL queries — better than Airtable when your data model has relationships between tables or you need server-side filtering at scale.
MongoDB Atlas Data API offers a document-store model with richer filtering operators — use it instead of Airtable when your records have deeply nested or variable-schema data that doesn't fit a flat spreadsheet grid.
Frequently asked questions
Can I use Airtable as the primary database for a production FlutterFlow app?
Airtable works well for apps where the data volume is modest (under a few thousand records per base) and write frequency is low. The 5 req/sec rate limit and 100-record pagination make it a poor fit for high-traffic apps or real-time features like chat or live inventory. For those scenarios, Supabase or Firebase are better choices. Airtable excels when your team already manages data in it and you want a simple app to display or submit that data.
Do I need a paid Airtable plan to use the API?
No — Airtable's free tier includes API access for up to 1,200 records per base. Personal Access Tokens are available on all plans including free. Paid plans (starting around $20/user/month — verify current pricing at airtable.com/pricing) unlock more records per base, longer revision history, and higher API rate limits on some plans.
Why does my ListView show data in Test Mode but not after I publish the app to web?
Test Mode routes API calls through FlutterFlow's internal proxy, bypassing the browser's CORS policy. Once you publish a web build, the Flutter app runs in the browser and is subject to CORS restrictions. Enable the 'Enable API Proxying' toggle in your API Group settings, or route calls through a Firebase Cloud Function to resolve this.
Can I update Airtable records from a FlutterFlow form?
Yes, but the write call must go through a Firebase Cloud Function to keep the write-scope Personal Access Token off the device. Build the function to accept a JSON payload with the record ID and updated fields, then call the Airtable PATCH endpoint server-side. In FlutterFlow, trigger this Cloud Function URL from a form's submit Action Flow.
What happens if a user has no internet connection — does Airtable data cache offline?
FlutterFlow's API Calls do not automatically cache data offline. If the device has no connection, the API call will fail and the ListView will be empty or show an error state. To add offline support, store the last-fetched records in a local SQLite variable or App State and show that cached data when the network call fails.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation