Connect Bolt.new to Airtable using the Airtable REST API with a personal access token — no OAuth required. Airtable acts as a spreadsheet-style database you can read and write from API routes. Fetch records for product catalogs, CRM data, or inventory. Create and update records from form submissions. Rate limit is 5 requests per second. Perfect for MVPs where non-technical team members manage data in a familiar spreadsheet interface.
Use Airtable as a Lightweight Database Backend for Your Bolt.new App
Airtable occupies a unique position in the no-code ecosystem: it is simultaneously a database and a spreadsheet, with a REST API that makes it accessible from any programming environment. For Bolt-built applications, this means you can have a non-technical product manager or operations team member manage your app's data — product listings, content, customer records, inventory, pricing — in a familiar spreadsheet interface while your Bolt app reads and displays that data dynamically through the API.
The Airtable REST API is notably simple compared to most database APIs. Authentication uses a single bearer token. Every endpoint follows the pattern `https://api.airtable.com/v0/{baseId}/{tableIdOrName}`. Records return as JSON arrays with a consistent structure: each record has an `id`, `createdTime`, and a `fields` object containing all column values. You can filter, sort, and paginate entirely through query parameters without writing a single line of SQL.
This simplicity makes Airtable an ideal database for MVPs and prototypes. You do not need to design a schema, run migrations, or configure Row Level Security. Create columns in the Airtable interface, point your Bolt app at the API, and start reading data in minutes. The main trade-off is scale — Airtable works well up to tens of thousands of records per base, but is not designed for high-volume transactional use cases. It is perfectly suited for product catalogs with hundreds of items, CRM databases with thousands of contacts, content management, and team-managed reference data.
Integration method
Bolt generates Next.js API routes that call Airtable's REST API using a personal access token stored in .env. Airtable's API is straightforward HTTP with JSON — no SDKs required, no complex authentication. All outbound calls to Airtable work in Bolt's WebContainer during development. Webhooks for real-time record change notifications require a deployed URL, but polling from the API route works fine in the preview for most use cases.
Prerequisites
- A Bolt.new account with a Next.js project
- An Airtable account at airtable.com (free plan works)
- An Airtable base with at least one table containing some records
- An Airtable personal access token with data.records:read and data.records:write scopes
Step-by-step guide
Create an Airtable Personal Access Token
Create an Airtable Personal Access Token
Airtable replaced its legacy API key system with personal access tokens in 2023. Personal access tokens offer granular scope control and can be restricted to specific bases, making them more secure than the old global API key. To create a token, log into Airtable and go to airtable.com/create/tokens. Click 'Create new token.' Give it a descriptive name like 'Bolt App.' Under Scopes, select the permissions your app needs. For reading records, select `data.records:read`. For creating and updating records, add `data.records:write`. If you need to read schema information (field names and types), add `schema.bases:read`. You can select multiple scopes. Under Access, choose whether the token has access to all your bases or specific ones. For a Bolt app, restricting to the specific base your app uses is a better security practice than granting access to all bases. Click the base name to restrict access, or leave 'All current and future bases' selected for simplicity during development. Click 'Create token' and immediately copy the token — it starts with `pat` and is only shown once at creation. Add it to your Bolt project's .env file as `AIRTABLE_API_KEY`. Also locate your Base ID. Open your Airtable base and look at the URL: it follows the pattern `https://airtable.com/{baseId}/{tableId}`. The Base ID starts with `app` — copy the portion starting with `app` (e.g., `appXXXXXXXXXXXXXX`). This goes in .env as `AIRTABLE_BASE_ID`. For the table, you can use either the table's display name (as it appears in the tab at the top of Airtable) or the table's ID (which starts with `tbl`). Using the table name is more readable; using the ID is more robust since renaming the table does not break your integration.
Add AIRTABLE_API_KEY and AIRTABLE_BASE_ID to the .env file. Create a lib/airtable.ts file that exports an airtableFetch helper. It should accept a table name, optional query params (filterByFormula, sort, maxRecords, fields), and return the records array. Use the Airtable REST API at https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{tableName} with Authorization: Bearer {AIRTABLE_API_KEY}. Handle the offset param for pagination to fetch all records beyond the 100-record default page size.
Paste this in Bolt.new chat
1// lib/airtable.ts2const AIRTABLE_BASE_URL = 'https://api.airtable.com/v0';34export interface AirtableRecord<T extends Record<string, unknown> = Record<string, unknown>> {5 id: string;6 createdTime: string;7 fields: T;8}910interface AirtableResponse<T> {11 records: AirtableRecord<T>[];12 offset?: string;13}1415interface AirtableFetchOptions {16 filterByFormula?: string;17 sort?: Array<{ field: string; direction?: 'asc' | 'desc' }>;18 fields?: string[];19 maxRecords?: number;20 view?: string;21}2223export async function airtableFetch<T extends Record<string, unknown> = Record<string, unknown>>(24 tableName: string,25 options: AirtableFetchOptions = {}26): Promise<AirtableRecord<T>[]> {27 const apiKey = process.env.AIRTABLE_API_KEY;28 const baseId = process.env.AIRTABLE_BASE_ID;2930 if (!apiKey || !baseId) {31 throw new Error('AIRTABLE_API_KEY and AIRTABLE_BASE_ID must be configured in .env');32 }3334 const allRecords: AirtableRecord<T>[] = [];35 let offset: string | undefined;3637 do {38 const params = new URLSearchParams();39 if (options.filterByFormula) params.set('filterByFormula', options.filterByFormula);40 if (options.view) params.set('view', options.view);41 if (options.maxRecords) params.set('maxRecords', String(options.maxRecords));42 if (offset) params.set('offset', offset);43 options.fields?.forEach((f) => params.append('fields[]', f));44 options.sort?.forEach((s, i) => {45 params.set(`sort[${i}][field]`, s.field);46 params.set(`sort[${i}][direction]`, s.direction || 'asc');47 });4849 const url = `${AIRTABLE_BASE_URL}/${baseId}/${encodeURIComponent(tableName)}?${params}`;50 const response = await fetch(url, {51 headers: { Authorization: `Bearer ${apiKey}` },52 });5354 if (!response.ok) {55 const error = await response.json().catch(() => ({})) as { error?: { message: string } };56 throw new Error(error?.error?.message || `Airtable API error: ${response.status}`);57 }5859 const data = await response.json() as AirtableResponse<T>;60 allRecords.push(...data.records);61 offset = data.offset;6263 // Respect rate limit: 5 requests/second64 if (offset) await new Promise((r) => setTimeout(r, 250));65 } while (offset && (!options.maxRecords || allRecords.length < options.maxRecords));6667 return allRecords;68}6970export async function airtableCreate<T extends Record<string, unknown>>(71 tableName: string,72 fields: Partial<T>73): Promise<AirtableRecord<T>> {74 const apiKey = process.env.AIRTABLE_API_KEY;75 const baseId = process.env.AIRTABLE_BASE_ID;7677 const response = await fetch(78 `${AIRTABLE_BASE_URL}/${baseId}/${encodeURIComponent(tableName)}`,79 {80 method: 'POST',81 headers: {82 Authorization: `Bearer ${apiKey}`,83 'Content-Type': 'application/json',84 },85 body: JSON.stringify({ fields }),86 }87 );8889 if (!response.ok) {90 const error = await response.json().catch(() => ({})) as { error?: { message: string } };91 throw new Error(error?.error?.message || `Airtable create error: ${response.status}`);92 }9394 return response.json() as Promise<AirtableRecord<T>>;95}9697export async function airtableUpdate<T extends Record<string, unknown>>(98 tableName: string,99 recordId: string,100 fields: Partial<T>101): Promise<AirtableRecord<T>> {102 const apiKey = process.env.AIRTABLE_API_KEY;103 const baseId = process.env.AIRTABLE_BASE_ID;104105 const response = await fetch(106 `${AIRTABLE_BASE_URL}/${baseId}/${encodeURIComponent(tableName)}/${recordId}`,107 {108 method: 'PATCH',109 headers: {110 Authorization: `Bearer ${apiKey}`,111 'Content-Type': 'application/json',112 },113 body: JSON.stringify({ fields }),114 }115 );116117 if (!response.ok) {118 const error = await response.json().catch(() => ({})) as { error?: { message: string } };119 throw new Error(error?.error?.message || `Airtable update error: ${response.status}`);120 }121122 return response.json() as Promise<AirtableRecord<T>>;123}Pro tip: Personal access tokens start with 'pat' and look like 'patXXXXXX.YYYYYY'. If you see a token starting with 'key', that is the old API key format which Airtable is phasing out. Create a new personal access token at airtable.com/create/tokens for new integrations.
Expected result: The airtable.ts helper is configured. Test by calling airtableFetch with your table name — it should return your records as a typed array. The helper handles pagination automatically for tables with more than 100 records.
Build a Records API Route for Your Table
Build a Records API Route for Your Table
With the helper library in place, create the API route that your React components will call. The route should accept query parameters for filtering and searching, call the airtableFetch helper, and return a clean JSON response. Adding a caching layer on the API route is important for Airtable because of the 5 requests-per-second rate limit — if multiple users load your page simultaneously, uncached requests can quickly hit the limit. Airtable's `filterByFormula` parameter accepts Airtable formula syntax. For simple equality filtering, use `{FieldName}='value'`. For text search, use `SEARCH(LOWER("query"), LOWER({FieldName}))`. For multiple conditions, combine with AND() or OR(): `AND({Status}='Active', {Category}='Electronics')`. Field names with spaces need curly braces: `{Product Name}`. The sort parameter on the list endpoint accepts an array of sort objects. When building a products catalog, sorting by a Number field (Price, Sort Order, Stock Count) or a Date field (CreatedTime, LastModified) is most common. Note that sorting by formula fields or lookup fields is not supported through the API — sort by stored fields only. For tables where real-time freshness matters less than performance (product catalogs, content, pricing), a 60-second cache on the API route dramatically reduces Airtable API calls and improves response times. Use a simple in-memory cache with a timestamp check — the same pattern as the Coinbase price example. For forms that write data, bypass the cache entirely on POST/PATCH requests. For large tables (thousands of records), the maxRecords parameter limits how many records the fetch returns. Combined with a meaningful sort, this gives you the most relevant records without fetching everything. For full pagination support, track the `offset` value returned by Airtable and include it in subsequent requests.
Create a Next.js API route at app/api/airtable/records/route.ts that fetches records from an Airtable table. Accept query params: table (required), filter (optional Airtable formula), search (optional text search against Name field), sortField, sortDirection. Cache responses for 60 seconds in memory (by table+filter key). Return { records: [...], total } with records having id and fields properties. Use the airtableFetch helper from lib/airtable.ts. Also create app/api/airtable/records/create/route.ts that accepts POST with { table, fields } and creates a new record.
Paste this in Bolt.new chat
1// app/api/airtable/records/route.ts2import { NextResponse } from 'next/server';3import { airtableFetch } from '@/lib/airtable';45// Simple in-memory cache6const cache = new Map<string, { data: unknown; expiresAt: number }>();7const CACHE_TTL = 60_000; // 60 seconds89export async function GET(request: Request) {10 const { searchParams } = new URL(request.url);11 const table = searchParams.get('table');12 const filter = searchParams.get('filter');13 const search = searchParams.get('search');14 const sortField = searchParams.get('sortField') || 'Name';15 const sortDirection = (searchParams.get('sortDirection') || 'asc') as 'asc' | 'desc';1617 if (!table) {18 return NextResponse.json({ error: 'table query parameter is required' }, { status: 400 });19 }2021 // Build filter formula22 let filterByFormula = filter || '';23 if (search) {24 const searchFormula = `SEARCH(LOWER("${search}"), LOWER({Name}))`;25 filterByFormula = filterByFormula26 ? `AND(${filterByFormula}, ${searchFormula})`27 : searchFormula;28 }2930 const cacheKey = `${table}|${filterByFormula}|${sortField}|${sortDirection}`;31 const cached = cache.get(cacheKey);32 if (cached && Date.now() < cached.expiresAt) {33 return NextResponse.json(cached.data);34 }3536 try {37 const records = await airtableFetch(table, {38 filterByFormula: filterByFormula || undefined,39 sort: [{ field: sortField, direction: sortDirection }],40 maxRecords: 500,41 });4243 const result = { records, total: records.length };44 cache.set(cacheKey, { data: result, expiresAt: Date.now() + CACHE_TTL });4546 return NextResponse.json(result);47 } catch (err) {48 const message = err instanceof Error ? err.message : 'Failed to fetch records';49 return NextResponse.json({ error: message }, { status: 500 });50 }51}Pro tip: If you see a 422 error with 'INVALID_FILTER_BY_FORMULA', the formula syntax is wrong. Airtable formulas use curly braces for field names ({FieldName}), not square brackets. Test your formula directly in Airtable's filter interface first, then copy the formula logic to your API route.
Expected result: The records API route fetches data from your Airtable table and returns it as JSON. The 60-second cache means rapid page reloads do not each consume a separate API quota request.
Build a Data-Driven UI Component
Build a Data-Driven UI Component
With the API route in place, build the React component that displays the Airtable data. The component should handle three states: loading (show skeleton cards or a spinner), error (show a retry button), and success (render the data). Use React's useEffect and useState hooks for data fetching, or a library like SWR for automatic revalidation and cache management. Airtable records come back with a `fields` object containing all column values. The field names in the API response match exactly the column names in your Airtable base — spaces included. If your column is named 'Product Name', the API returns `record.fields['Product Name']`. If it is named 'ProductName' (no space), it is `record.fields.ProductName`. This means renaming columns in Airtable changes the API response keys, so coordinate column naming carefully. For attachment fields (images, documents), Airtable returns an array of attachment objects each with a `url`, `filename`, `type`, and `thumbnails` property. Thumbnails include small, large, and full sizes. Use the thumbnail URL for product cards and the full URL for detail views — this significantly reduces bandwidth compared to loading full-size images in list contexts. For linked record fields (relationships between tables), the API returns an array of record IDs: `['recXXXX', 'recYYYY']`. To display the linked record's content, you need a separate API call to fetch those records. For simple dashboards, flatten the relationship at build time by fetching both tables and joining them in your API route before returning data to the client.
Build a ProductCatalog React component that fetches from /api/airtable/records?table=Products. Display products as a responsive grid of cards. Each card should show: the first attachment image (from the Images field, use the large thumbnail URL), Product Name, Price formatted as USD currency, Category as a colored badge, and Description truncated to 2 lines. Add a search input that filters the displayed cards client-side. Add a category filter row of buttons at the top. Show a loading skeleton of 8 placeholder cards while fetching. Handle empty state and error state.
Paste this in Bolt.new chat
Pro tip: Airtable attachment URLs expire after a few hours — do not store these URLs in your own database or cache. Always fetch fresh attachment URLs from the Airtable API when you need to display images. The thumbnail URL changes on each API response but is always valid when received.
Expected result: The product catalog renders with real data from Airtable, including images from attachment fields. The search input filters cards in real time and the category filter buttons narrow the displayed products.
Handle Record Creation and Updates
Handle Record Creation and Updates
Creating a record in Airtable is a POST request to `https://api.airtable.com/v0/{baseId}/{tableName}` with a JSON body of `{ fields: { FieldName: value, ... } }`. The response returns the newly created record with its `id` and `createdTime`. Unlike some APIs, Airtable does not require all fields to be present — omit any fields you want to remain empty. Updating a record is a PATCH request to `https://api.airtable.com/v0/{baseId}/{tableName}/{recordId}` with only the fields you want to change. PATCH performs a partial update. Alternatively, PUT replaces the entire record, setting all unspecified fields to null — use PATCH for typical edits. For form-based record creation, validate data before calling the Airtable API. Airtable returns 422 errors for type mismatches (e.g., sending a string where a number is expected) and for required field violations (if you've configured required fields in Airtable's field settings). Display these errors to the user clearly. After a successful write operation, invalidate the relevant cache key on your API route so the next read reflects the new data. For the in-memory cache, this means deleting the cache entry. This ensures users see their just-submitted records immediately rather than waiting for the cache TTL. For bulk creation (creating multiple records at once), Airtable supports up to 10 records in a single POST by wrapping them in an array: `{ records: [{ fields: {...} }, { fields: {...} }] }`. This is significantly more efficient than individual requests when importing or syncing data from another source.
Create a form component and API route for creating Airtable records. Build /api/airtable/records/create/route.ts that accepts POST with { table, fields }. Call airtableCreate from lib/airtable.ts and return the new record. Also build /api/airtable/records/[id]/route.ts that handles PATCH requests with { table, fields } to update an existing record. Build a CreateRecordForm React component with fields for Name, Email, and Status (select from Active/Inactive/Pending). On submit, POST to the create endpoint and show a success toast with the new record ID.
Paste this in Bolt.new chat
1// app/api/airtable/records/create/route.ts2import { NextResponse } from 'next/server';3import { airtableCreate } from '@/lib/airtable';45export async function POST(request: Request) {6 const { table, fields } = await request.json() as { table: string; fields: Record<string, unknown> };78 if (!table || !fields) {9 return NextResponse.json({ error: 'table and fields are required' }, { status: 400 });10 }1112 try {13 const record = await airtableCreate(table, fields);14 return NextResponse.json({ success: true, record });15 } catch (err) {16 const message = err instanceof Error ? err.message : 'Failed to create record';17 // Check for common Airtable errors18 const isTypeError = message.includes('INVALID_VALUE_FOR_COLUMN');19 const isRequiredError = message.includes('CANNOT_BE_NULL');20 return NextResponse.json(21 {22 error: message,23 hint: isTypeError24 ? 'A field value has the wrong type — check that numbers are numbers, not strings'25 : isRequiredError26 ? 'A required field is missing — check all required columns in your Airtable schema'27 : undefined,28 },29 { status: isTypeError || isRequiredError ? 422 : 500 }30 );31 }32}Pro tip: To avoid rate limit errors on high-traffic form submissions, add a minimum delay between rapid successive requests. Five requests per second is Airtable's limit across all API keys for a base — if your app can receive multiple simultaneous form submissions, implement a simple queue or a short debounce.
Expected result: The form successfully creates records in Airtable. Refreshing the Airtable base in your browser shows the new record. The API route returns the created record's ID and all fields including createdTime.
Common use cases
Product Catalog from Airtable
Use an Airtable base as a product catalog backend. Non-technical team members add, edit, and manage products directly in Airtable's spreadsheet interface — setting prices, uploading images, toggling availability, and managing categories. Your Bolt app fetches this data via API and displays it as a styled product grid or list, always showing the latest information without any code changes.
Build a product catalog page that reads from Airtable. Create a Next.js API route at /api/airtable/products that fetches all records from a Products table, filtering to only records where Status field equals 'Active'. Sort by CreatedTime descending. Return id, Name, Price, Description, Category, Image URL, and InStock fields. Build a React product grid with category filter buttons and a search bar. Each product card shows the image, name, price formatted as currency, and an 'In Stock' or 'Out of Stock' badge. Store AIRTABLE_API_KEY and AIRTABLE_BASE_ID in process.env.
Copy this prompt to try it in Bolt.new
Internal CRM with Airtable Backend
Build a lightweight internal CRM where your team manages customer contacts in Airtable and your Bolt app provides a cleaner, role-specific interface for viewing and updating records. Filter contacts by status or source, log notes as a new record in a related Notes table, and display a timeline of customer interactions — all without the complexity of a full CRM subscription.
Create an internal CRM view using Airtable as the database. Build a /api/airtable/contacts route that fetches Contact records from Airtable with fields: Name, Email, Company, Status, LeadSource, LastContact, Notes. Support a search param that uses Airtable's filterByFormula to search Name and Email. Build a React contacts list with search, a status filter dropdown (Lead, Prospect, Customer, Churned), and a detail panel that slides in when a contact is clicked. Include a quick-edit form that updates the Status and Notes fields via a PATCH request to /api/airtable/contacts/[id].
Copy this prompt to try it in Bolt.new
Event Registration Form with Airtable Storage
Build an event registration form that stores submissions directly in an Airtable base. Collect attendee information, check for duplicate registrations, confirm capacity limits from an Airtable record, and write new registrations as records. Team members review and manage registrations in Airtable's familiar interface while your Bolt app handles the public-facing form.
Build an event registration form that saves to Airtable. Create a Next.js API route at /api/airtable/register that accepts POST with { name, email, company, ticketType }. First check for duplicate email using filterByFormula. Then check remaining capacity from an Event record. If valid, create a new Registration record in Airtable with Status='Pending'. Return { registered: true, confirmationId } on success. Build a React form with name, email, company, and ticket type (General/VIP/Speaker) fields. Show a confirmation message with a reference number after successful submission.
Copy this prompt to try it in Bolt.new
Troubleshooting
API returns 401 Unauthorized even with the correct token in the Authorization header
Cause: The personal access token is malformed or has insufficient scopes. Common causes: the token was copied with leading/trailing spaces, the AIRTABLE_API_KEY variable has the NEXT_PUBLIC_ prefix making it a client-side variable that gets bundled but also exposed, or the token's scopes do not include data.records:read.
Solution: Verify the token starts with 'pat' and has no spaces. Check that AIRTABLE_API_KEY does NOT have the NEXT_PUBLIC_ prefix in .env. Regenerate the token at airtable.com/create/tokens if needed. When regenerating, explicitly select data.records:read and data.records:write scopes and restrict to your specific base.
Rate limit error — 429 Too Many Requests after several API calls
Cause: Airtable's rate limit is 5 requests per second per base. Simultaneous requests from multiple users or rapid pagination requests can exceed this limit.
Solution: Implement the 60-second in-memory cache on your API routes. Add a 250ms delay between pagination requests in the airtableFetch helper. For high-traffic apps, consider syncing Airtable data to your own database on a schedule rather than querying Airtable on every user request.
1// Add delay between paginated requests in lib/airtable.ts:2if (offset) await new Promise((r) => setTimeout(r, 250)); // 250ms between pagesRecords come back but with empty field names or unexpected field keys
Cause: Airtable field names in the API response match the column names exactly as configured in the Airtable base, including capitalization and spaces. A column renamed in Airtable will change the key name in the API response.
Solution: Check your column names in Airtable. The API uses the exact column name as the key — 'Product Name' in Airtable becomes record.fields['Product Name'] in JavaScript. Use TypeScript interfaces that define the expected field structure and update them if column names change.
1// Define typed interfaces matching your Airtable column names exactly:2interface ProductFields {3 'Product Name': string; // Exact column name including spaces4 Price: number;5 Category: string;6 Images?: Array<{ url: string; thumbnails: { large: { url: string } } }>;7}8const records = await airtableFetch<ProductFields>('Products');Airtable webhook events are not arriving at the API route during development
Cause: Airtable webhooks send POST requests to your server. The Bolt WebContainer preview URL cannot receive incoming connections from Airtable's servers.
Solution: Deploy to Netlify or Bolt Cloud to get a stable URL. Register that deployed URL as the webhook callback in Airtable's automations or via the Airtable Webhooks API. For development, use polling from the API route on an interval rather than webhooks — most Airtable use cases do not require real-time webhooks.
Best practices
- Use personal access tokens scoped to the minimum required permissions and specific bases rather than creating tokens with access to all bases and all scopes.
- Never use the NEXT_PUBLIC_ prefix for AIRTABLE_API_KEY — this would expose your token in client-side JavaScript. Always access Airtable from server-side API routes only.
- Cache frequently accessed Airtable data for 60 seconds or more — the 5 requests-per-second rate limit is easy to hit with multiple concurrent users, and most Airtable-backed data (product catalogs, content) does not change faster than every minute.
- Design column names in Airtable with code maintainability in mind — use CamelCase or PascalCase without spaces (ProductName, not 'Product Name') to make TypeScript interfaces cleaner, since spaces in field names require bracket notation in JavaScript.
- Always add 250ms delays between paginated API requests when fetching more than 100 records — the automatic pagination in the helper must respect rate limits or risk 429 errors that break the pagination loop.
- Implement duplicate detection before creating records from form submissions — search for an existing record matching the primary identifier (email, name + company) before creating a new one to avoid polluting the Airtable base with duplicates.
- For attachment fields (images), always use Airtable-provided thumbnail URLs for card grids and save full-size URLs for detail views — this dramatically reduces page load times for catalog pages with many images.
- Use Airtable Views to pre-filter and pre-sort records for specific use cases — the API supports a view parameter that applies all the filters and sorts from a named view, reducing the complexity of filter formulas in your code.
Alternatives
Notion's API also provides database functionality with a familiar interface, but Airtable's spreadsheet-like layout and more powerful filtering make it better for structured tabular data.
Firebase Firestore offers real-time sync and is better for high-volume transactional data, while Airtable is preferred when non-technical team members need to manage data directly.
Google Looker Studio is for analytics visualization rather than editable data management, so Airtable is a better choice when you need a backend your team can write to.
MongoDB Atlas Data API works over HTTP like Airtable, but requires technical setup; Airtable is better when non-developers need to manage the underlying data.
Frequently asked questions
Can I use the Airtable API in Bolt's WebContainer preview?
Yes — all outbound calls to Airtable's REST API work from Next.js API routes in the Bolt WebContainer. You can read and write records during development without deploying. The only Airtable feature that requires deployment is webhook callbacks, since those are incoming connections that cannot reach the WebContainer.
Does Bolt.new have a native Airtable integration?
No — Bolt.new does not have a built-in Airtable connector. The integration uses a simple personal access token and direct REST API calls from Next.js API routes. Bolt's AI can generate the full integration from a prompt, making it quick to set up even without a native connector.
Is the Airtable API free to use?
Airtable's REST API is available on all plans including the free tier. Free plans have 1,000 records per base and 1,200 API calls per month. Paid plans (Plus at $10/user/month, Pro at $20/user/month) increase record limits and API call quotas significantly. For development and small production apps, the free tier is usually sufficient.
How do I handle Airtable field names with spaces in TypeScript?
Airtable field names in the API response use the exact column name including spaces. In TypeScript, define your interface with quoted key names: { 'Product Name': string; 'Created Time': string; }. Access values using bracket notation: record.fields['Product Name']. For cleaner code, consider naming Airtable columns without spaces using camelCase or PascalCase.
Can I display real-time Airtable data updates in my Bolt app?
Near-real-time updates can be achieved by polling your API route every 30-60 seconds from a useEffect with setInterval. True real-time push updates require Airtable webhooks, which send POST requests to your server — this works after deployment to Netlify or Bolt Cloud but not in the WebContainer preview. For most use cases, 60-second polling is sufficient.
What is the maximum number of records I can fetch from Airtable at once?
Each Airtable API request returns up to 100 records per page. For tables with more than 100 records, use the offset pagination token in the response to fetch subsequent pages. The airtableFetch helper in the code example handles this automatically, but adds a 250ms delay between pages to stay within the 5 requests-per-second rate limit. Use maxRecords to cap total fetched records for performance.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation