Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Airtable with V0

To connect Airtable to a V0 by Vercel app, create a Next.js API route at app/api/airtable/route.ts that uses the Airtable REST API with your Personal Access Token stored in AIRTABLE_API_KEY. Store your base ID and table name in environment variables. V0 generates the React components that fetch from your API route to display Airtable records as tables, cards, or any layout you design.

What you'll learn

  • How to generate a Personal Access Token in Airtable and store it securely in Vercel
  • How to create a Next.js API route that reads Airtable records
  • How to prompt V0 to generate table and card components that display Airtable data
  • How to filter, sort, and paginate Airtable records through the API
  • How to write new records to Airtable from a V0 form component
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate17 min read20 minutesDatabaseMarch 2026RapidDev Engineering Team
TL;DR

To connect Airtable to a V0 by Vercel app, create a Next.js API route at app/api/airtable/route.ts that uses the Airtable REST API with your Personal Access Token stored in AIRTABLE_API_KEY. Store your base ID and table name in environment variables. V0 generates the React components that fetch from your API route to display Airtable records as tables, cards, or any layout you design.

Using Airtable as a Backend for Your V0 App

Airtable occupies a unique position as a backend for V0 apps: it is powerful enough to handle real product data, but accessible enough that non-technical team members can manage it directly without touching code. If you have a product catalog, a CRM, a content calendar, or any structured dataset that a team member needs to edit regularly, Airtable is often the right choice over a traditional database like PostgreSQL.

The integration pattern is straightforward. Airtable exposes a REST API that you call from a Next.js API route. Your Airtable Personal Access Token is stored in Vercel's environment variables — never in client-side code. V0 generates the front-end components that fetch from your API route and render the data in whatever format makes sense: a data table, a card grid, a directory listing, or a dynamic dashboard.

Airtable's flexibility makes it especially useful for apps where the data model is still evolving. Adding a new field in Airtable requires no schema migration — you just update your API route to include the new field in the response and prompt V0 to display it. This makes Airtable a popular backend choice for early-stage products where requirements change frequently. The tradeoff is scale: Airtable is not designed for apps with hundreds of thousands of records or high-frequency writes. For those cases, PostgreSQL via Vercel's Neon integration is a better fit.

Integration method

Next.js API Route

V0 generates React components that display your Airtable data. A Next.js API route running on Vercel proxies requests to the Airtable REST API, keeping your Personal Access Token server-side and handling CORS automatically. Your components call /api/airtable/records rather than Airtable directly, which works in any browser without credential exposure.

Prerequisites

  • A V0 account with a Next.js project at v0.dev
  • An Airtable account at airtable.com with at least one base and one table containing records
  • An Airtable Personal Access Token generated at airtable.com/create/tokens
  • Your Airtable Base ID (found in the Airtable API docs for your specific base, starts with 'app')
  • A Vercel account with your V0 project deployed via GitHub

Step-by-step guide

1

Get Your Airtable API Credentials

You need two pieces of information from Airtable before writing any code: your Personal Access Token (PAT) and your Base ID. Both are available from the Airtable web interface. First, generate a Personal Access Token. Go to airtable.com/create/tokens (you can also reach this from your Airtable profile icon → Developer Hub → Personal access tokens). Click Create new token. Give it a descriptive name like 'V0 Frontend App'. For scopes, select at minimum data.records:read to fetch records. If you also want to write records (for forms), add data.records:write. Under Access, choose which specific bases the token can access — selecting only the base you need is more secure than granting access to all bases. Click Create token and copy the token value immediately — Airtable only shows it once. Second, find your Base ID. Go to airtable.com/api in a browser (you must be logged in). You will see a list of your bases. Click on the base you want to use. The URL of the API documentation page for that base will look like https://airtable.com/appXXXXXXXXXXXX/api/docs — the appXXXXXXXXXXXX part is your Base ID. Copy it. Finally, note the exact name of the table you want to query. Table names are case-sensitive in the Airtable API. If your table is called 'Products' in Airtable, your API calls must use exactly 'Products'. Check the name in your Airtable base to be sure — it appears as the tab name at the top of the table view. Keep these three values (PAT, Base ID, table name) handy — you will use them in the next steps.

Pro tip: Create Airtable Personal Access Tokens with the minimum scope required. A read-only token cannot be used to accidentally delete or corrupt your data.

Expected result: You have your Airtable Personal Access Token (starts with 'pat'), your Base ID (starts with 'app'), and the exact table name. These are ready to add to Vercel environment variables.

2

Add Environment Variables to Vercel

Your Next.js API route will read Airtable credentials from environment variables. Add them to Vercel before writing the code, so the environment is ready when you deploy. Go to your Vercel Dashboard, open your project, click the Settings tab, and select Environment Variables from the left sidebar. Add the following variables: AIRTABLE_API_KEY: Set this to your Airtable Personal Access Token (the value starting with 'pat'). This is a secret — set it for Production, Preview, and Development environments. Never add a NEXT_PUBLIC_ prefix to this variable — that would expose the token in your browser-side JavaScript bundle. AIRTABLE_BASE_ID: Set this to your Base ID (the value starting with 'app'). This is not strictly a secret (it is visible in URLs when you share Airtable views), but keeping it in an environment variable makes it easy to change. AIRTABLE_TABLE_NAME: Set this to the exact name of your Airtable table. For example: Products, Team Members, or Inventory. Case-sensitive. Click Save for each variable. After adding all three, trigger a redeployment of your Vercel project by pushing any commit to your GitHub repository, or by clicking Redeploy in the Vercel dashboard. For local development, create a .env.local file in your project root with the same three variables. The V0-generated .gitignore already excludes .env.local, so this file will not be committed to GitHub.

.env.local
1# .env.local local development only, excluded from Git
2AIRTABLE_API_KEY=patXXXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3AIRTABLE_BASE_ID=appXXXXXXXXXXXXXX
4AIRTABLE_TABLE_NAME=Products

Pro tip: If you have multiple tables to query, add separate TABLE_NAME variables like AIRTABLE_PRODUCTS_TABLE and AIRTABLE_LEADS_TABLE rather than one generic variable.

Expected result: Vercel Dashboard shows AIRTABLE_API_KEY, AIRTABLE_BASE_ID, and AIRTABLE_TABLE_NAME saved as environment variables. Local .env.local file is configured for development.

3

Create the Airtable API Route

Create the Next.js API route that fetches records from Airtable. This route runs on Vercel's serverless infrastructure and is never exposed to the browser, making it safe to use your Airtable API key here. Create the file app/api/airtable/records/route.ts. The route calls the Airtable REST API directly using the native fetch() function. The Airtable API endpoint for listing records is: https://api.airtable.com/v0/{baseId}/{tableName}. The Authorization header must include your PAT as a Bearer token. Airtable's API supports several useful query parameters: filterByFormula for filtering records based on field values, sort for specifying sort field and direction, maxRecords to limit the number of records returned, and pageSize for pagination. These are passed as query string parameters on the Airtable API URL. Airtable returns records in a paginated format — the response object has a records array and optionally an offset string. If offset is present in the response, there are more records to fetch. You can pass the offset back as a query parameter on the next request to get the next page. For most small-to-medium use cases, setting maxRecords=100 retrieves enough records in a single request without needing to handle pagination. The response from Airtable wraps each record in an object with id (the Airtable record ID), createdTime, and fields (an object with your column names as keys). Transform the response to flatten the fields for easier consumption in your V0 components — return an array of objects where each object combines the record ID with the field values.

V0 Prompt

Create a Next.js API route at app/api/airtable/records/route.ts that fetches records from Airtable. Use AIRTABLE_API_KEY, AIRTABLE_BASE_ID, and AIRTABLE_TABLE_NAME from environment variables. Support a 'filter' query parameter passed as filterByFormula. Return records with their id and all field values flattened. Handle errors with a 500 response.

Paste this in V0 chat

app/api/airtable/records/route.ts
1import { NextRequest, NextResponse } from 'next/server';
2
3interface AirtableRecord {
4 id: string;
5 createdTime: string;
6 fields: Record<string, unknown>;
7}
8
9interface AirtableResponse {
10 records: AirtableRecord[];
11 offset?: string;
12}
13
14export async function GET(request: NextRequest) {
15 const apiKey = process.env.AIRTABLE_API_KEY;
16 const baseId = process.env.AIRTABLE_BASE_ID;
17 const tableName = process.env.AIRTABLE_TABLE_NAME;
18
19 if (!apiKey || !baseId || !tableName) {
20 return NextResponse.json(
21 { error: 'Airtable configuration missing' },
22 { status: 500 }
23 );
24 }
25
26 const { searchParams } = new URL(request.url);
27 const filter = searchParams.get('filter') || '';
28 const sort = searchParams.get('sort') || 'Name';
29 const maxRecords = searchParams.get('maxRecords') || '100';
30
31 // Build Airtable API URL
32 const airtableUrl = new URL(
33 `https://api.airtable.com/v0/${baseId}/${encodeURIComponent(tableName)}`
34 );
35 airtableUrl.searchParams.set('maxRecords', maxRecords);
36 if (filter) {
37 airtableUrl.searchParams.set('filterByFormula', filter);
38 }
39 airtableUrl.searchParams.set('sort[0][field]', sort);
40 airtableUrl.searchParams.set('sort[0][direction]', 'asc');
41
42 try {
43 const response = await fetch(airtableUrl.toString(), {
44 headers: {
45 Authorization: `Bearer ${apiKey}`,
46 'Content-Type': 'application/json',
47 },
48 next: { revalidate: 60 }, // Cache for 60 seconds
49 });
50
51 if (!response.ok) {
52 const error = await response.text();
53 console.error('Airtable API error:', error);
54 throw new Error(`Airtable returned ${response.status}`);
55 }
56
57 const data: AirtableResponse = await response.json();
58
59 // Flatten records: combine id with fields for easier component use
60 const records = data.records.map((record) => ({
61 id: record.id,
62 createdTime: record.createdTime,
63 ...record.fields,
64 }));
65
66 return NextResponse.json({ records, total: records.length });
67 } catch (error) {
68 console.error('Failed to fetch Airtable records:', error);
69 return NextResponse.json(
70 { error: 'Failed to fetch records' },
71 { status: 500 }
72 );
73 }
74}

Pro tip: The next: { revalidate: 60 } option caches the Airtable response for 60 seconds. Increase this for data that changes rarely, decrease it for real-time dashboards.

Expected result: Calling /api/airtable/records returns a JSON object with a records array containing flattened Airtable field data. Each record has an id field plus all your Airtable column values.

4

Generate Display Components with V0

With the API route returning data, prompt V0 to generate the React components that display your Airtable records. The key to effective V0 prompts for data-driven components is specifying the exact field names from your Airtable table and the layout you want. Before prompting V0, visit /api/airtable/records in your browser (or after deploying, on your Vercel preview URL) to see the actual JSON structure returned. Note the exact field names — for example, if your Airtable table has a column called 'Product Name', the field key in the API response will be 'Product Name' with a space, not 'productName'. Copy these field names exactly to use in your V0 prompt. Tell V0 what the data structure looks like, what fields to display, and what layout to use. For a simple list, a table component works well. For rich content with images, a card grid is better. You can also ask V0 to add client-side filtering and sorting so users can explore the data without triggering additional API calls. Ask V0 to add a loading state (skeleton cards or a spinner) that shows while the fetch is in progress, and an error state that displays a friendly message if the API call fails. These polish the user experience significantly. Also ask V0 to handle empty states — a message like 'No records found' when the records array is empty. For searchable directories or catalogs, ask V0 to add a search input that filters the already-fetched records client-side. This avoids making additional API calls for every keystroke and provides an instant-feeling search experience for datasets under a few hundred records.

V0 Prompt

Create a product directory page that fetches data from /api/airtable/records on page load. The data has fields: 'Product Name', 'Price', 'Description', 'Category', 'Image URL'. Display products as cards in a 3-column responsive grid. Each card shows the image (with a grey placeholder if Image URL is empty), Product Name as bold heading, Price in green, Category as a small badge, and Description as 2 lines of truncated text. Add a search input above the grid that filters by Product Name client-side. Show loading skeleton cards while fetching and an error message if the fetch fails.

Paste this in V0 chat

Pro tip: If your Airtable field names have spaces (like 'Product Name'), access them in JavaScript with bracket notation: record['Product Name']. V0 should handle this correctly if you specify the field names in your prompt.

Expected result: V0 generates a polished product directory (or equivalent) component that fetches from /api/airtable/records and renders each record in the layout you specified, with loading and error states.

5

Add a Write Route for Form Submissions

Many Airtable integrations need to write data back, not just read it. Contact forms, lead capture forms, waitlist signups, and feedback submissions are all common patterns where a V0 form component sends data to an API route that creates a new Airtable record. Create a POST handler in the same API route file (or a separate file at app/api/airtable/leads/route.ts for clarity). The Airtable REST API creates records by sending a POST request to https://api.airtable.com/v0/{baseId}/{tableName} with a JSON body containing a records array. Each record in the array has a fields object with the column name-value pairs to write. The V0 form component should POST to this API route with a JSON body containing the form field values. The API route validates the input, maps the field names to Airtable column names, and calls the Airtable API. On success, return a 200 response. On failure, return an appropriate error so the V0 component can show an error message to the user. For security, validate all incoming data server-side in the API route before writing to Airtable. Check that required fields are present, that email addresses match a valid format, and that text fields are within reasonable length limits. This prevents spam or malformed data from reaching your Airtable base. For complex integrations combining Airtable with email notifications, CRM workflows, or multi-step forms across several Airtable tables, RapidDev's team can help design the data architecture and API layer to keep everything organized.

V0 Prompt

Add a contact form below the product directory with fields for Full Name, Email, Company, and a Message textarea. On submit, POST the form data to /api/airtable/leads. Show a green success banner if the submission succeeds and a red error message if it fails. Disable the submit button and show a loading state while the request is in progress.

Paste this in V0 chat

app/api/airtable/leads/route.ts
1import { NextRequest, NextResponse } from 'next/server';
2
3interface LeadFormData {
4 fullName: string;
5 email: string;
6 company?: string;
7 message: string;
8}
9
10export async function POST(request: NextRequest) {
11 const apiKey = process.env.AIRTABLE_API_KEY;
12 const baseId = process.env.AIRTABLE_BASE_ID;
13
14 // Use a separate table name env var for leads, or fall back to default
15 const tableName = process.env.AIRTABLE_LEADS_TABLE || 'Leads';
16
17 if (!apiKey || !baseId) {
18 return NextResponse.json(
19 { error: 'Airtable configuration missing' },
20 { status: 500 }
21 );
22 }
23
24 let body: LeadFormData;
25 try {
26 body = await request.json();
27 } catch {
28 return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
29 }
30
31 const { fullName, email, company, message } = body;
32
33 // Server-side validation
34 if (!fullName || !email || !message) {
35 return NextResponse.json(
36 { error: 'Name, email, and message are required' },
37 { status: 400 }
38 );
39 }
40
41 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
42 if (!emailRegex.test(email)) {
43 return NextResponse.json({ error: 'Invalid email address' }, { status: 400 });
44 }
45
46 const airtableUrl = `https://api.airtable.com/v0/${baseId}/${encodeURIComponent(tableName)}`;
47
48 try {
49 const response = await fetch(airtableUrl, {
50 method: 'POST',
51 headers: {
52 Authorization: `Bearer ${apiKey}`,
53 'Content-Type': 'application/json',
54 },
55 body: JSON.stringify({
56 records: [
57 {
58 fields: {
59 'Full Name': fullName,
60 Email: email,
61 Company: company || '',
62 Message: message,
63 'Submitted At': new Date().toISOString(),
64 },
65 },
66 ],
67 }),
68 });
69
70 if (!response.ok) {
71 const error = await response.text();
72 console.error('Airtable write error:', error);
73 throw new Error(`Airtable returned ${response.status}`);
74 }
75
76 return NextResponse.json({ success: true });
77 } catch (error) {
78 console.error('Failed to write to Airtable:', error);
79 return NextResponse.json(
80 { error: 'Failed to submit. Please try again.' },
81 { status: 500 }
82 );
83 }
84}

Pro tip: Add a 'Submitted At' field to your Airtable Leads table (Date/time type) so you can see exactly when each form submission arrived.

Expected result: Submitting the contact form creates a new record in your Airtable Leads table with all the form field values. The Vercel logs confirm the write API call succeeded.

Common use cases

Product Directory from Airtable Base

A founder manages their product catalog in Airtable with columns for product name, price, description, category, and image URL. V0 generates a searchable product directory page that fetches from the Airtable API route. The team updates products directly in Airtable and the website reflects changes within seconds.

V0 Prompt

Build a product directory page that fetches records from /api/airtable/records. Display each product as a card with name, price badge, description, category tag, and product image. Include a search input that filters products by name. Use a responsive 3-column grid layout on desktop.

Copy this prompt to try it in V0

Team Directory or Member Roster

A community organization manages their member roster in Airtable with fields for name, role, bio, photo URL, and contact email. V0 generates a public-facing team page that displays members as profile cards. Adding a new member in Airtable automatically adds them to the website.

V0 Prompt

Create a team directory page that loads member data from /api/airtable/records. Show each member as a profile card with their photo (use a placeholder if photo is empty), full name as heading, role subtitle, short bio, and a mailto link for their email. Sort by the Name field alphabetically.

Copy this prompt to try it in V0

Contact Form That Writes to Airtable

A startup uses Airtable as a lightweight CRM to track leads. V0 generates a contact form that submits lead data (name, email, company, message) to a Next.js API route, which creates a new record in the Airtable base. The sales team reviews and responds to leads directly from the Airtable spreadsheet view.

V0 Prompt

Build a contact form with fields for full name, email address, company name, and a message textarea. The form should POST to /api/airtable/leads on submission. Show a success message after successful submission and an error message if the request fails. Validate that name and email are required fields.

Copy this prompt to try it in V0

Troubleshooting

API route returns 401 Unauthorized from Airtable

Cause: The Airtable Personal Access Token is missing, incorrect, or the token does not have the required scope (data.records:read) for the base being accessed.

Solution: Go to airtable.com/create/tokens and verify your token is active. Check that it has data.records:read scope and access to the correct base. Copy the full token value (it starts with 'pat') and update the AIRTABLE_API_KEY environment variable in Vercel. Redeploy after updating.

API route returns 404 with 'TABLE_NOT_FOUND' from Airtable

Cause: The table name in AIRTABLE_TABLE_NAME does not exactly match the table name in Airtable. The Airtable API is case-sensitive for table names.

Solution: Open your Airtable base and check the exact tab name at the top of the table view. Update AIRTABLE_TABLE_NAME in Vercel to match exactly, including capitalization and spaces. 'Products' and 'products' are different table names.

Records fetch but field values are all undefined or missing

Cause: Your V0 component is accessing field names using camelCase (like record.productName) but Airtable field names preserve spaces and capitalization (like record['Product Name']).

Solution: Check the exact field names by logging the raw API response to the Vercel console. Update your V0 component to access fields using bracket notation with exact names: record['Product Name'], record['Price'], etc. Alternatively, add a transformation step in the API route to convert field names to camelCase.

typescript
1// Access Airtable fields with spaces using bracket notation
2const name = record['Product Name']; // correct
3const name2 = record.productName; // undefined — wrong

CORS error when the browser tries to call Airtable directly

Cause: A V0-generated component is calling the Airtable API directly from the browser (client side) instead of going through the Next.js API route.

Solution: Update the fetch URL in your React component from https://api.airtable.com/... to /api/airtable/records. All Airtable calls must go through your Next.js API route which runs server-side on Vercel where CORS restrictions do not apply.

typescript
1// WRONG — exposes API key, causes CORS errors
2fetch('https://api.airtable.com/v0/appXXX/Products', {
3 headers: { Authorization: `Bearer ${apiKey}` }
4})
5
6// CORRECT — proxied through your Next.js API route
7fetch('/api/airtable/records')

Best practices

  • Never expose your Airtable Personal Access Token in client-side code — always proxy Airtable API calls through a Next.js API route.
  • Create Airtable PATs with the minimum required scope: read-only tokens for display pages, write tokens only for form submission routes.
  • Add next: { revalidate: 60 } to your Airtable fetch calls to cache results and reduce API request volume.
  • Use exact Airtable field names (case-sensitive, with spaces) when building your API route — verify against the actual Airtable base.
  • Add server-side input validation in your write API routes before sending data to Airtable to prevent spam and malformed records.
  • For large Airtable bases with hundreds of records, implement proper pagination using Airtable's offset parameter rather than fetching all records at once.
  • Keep separate Airtable tables for different data types (Products, Leads, Orders) and create separate API routes for each table.

Alternatives

Frequently asked questions

Does V0 have a native Airtable integration or connector?

No. V0 does not have a built-in Airtable connector. The integration is built manually using a Next.js API route that calls the Airtable REST API. This gives you full control over which fields are exposed, how data is transformed, and what caching behavior is applied.

What is the difference between an Airtable API key and a Personal Access Token?

Airtable deprecated legacy API keys in early 2024. Personal Access Tokens (PATs) replaced them. PATs start with 'pat' and are more secure because you can scope them to specific bases and operations. If you see tutorials using older API key format starting with 'key', those no longer work — you need to generate a PAT at airtable.com/create/tokens.

How many records can Airtable return in a single API request?

Airtable's REST API returns a maximum of 100 records per page. If your table has more than 100 records, the response includes an offset field. You can fetch the next page by making another request with offset as a query parameter. For most small apps, setting maxRecords=100 retrieves all data in a single request without needing pagination logic.

Can I use Airtable's formula fields in my API response?

Yes. Airtable formula fields are returned in the API response just like regular fields. If you have a formula field called 'Full Price' that computes a total, it appears in the record's fields object. You do not need to re-calculate formulas in your API route — just read the field value like any other.

What happens if I hit Airtable's API rate limits?

Airtable's REST API allows 5 requests per second per base on the free tier. If you exceed this, the API returns a 429 Too Many Requests error. Adding next: { revalidate: 60 } to your fetch calls in the API route caches the response for 60 seconds, dramatically reducing the number of actual Airtable API calls. For high-traffic pages, increase the revalidation time to several minutes.

Can I connect multiple Airtable bases to a single V0 app?

Yes. Create separate environment variables for each base (AIRTABLE_PRODUCTS_BASE_ID, AIRTABLE_CRM_BASE_ID) and separate API routes for each table. Your Personal Access Token can have access to multiple bases, so you can use the same token for all routes. This is common for apps that use one Airtable base as a CMS and another as a CRM.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.