Skip to main content
Guides

Airtable Agent

This guide shows how to build event-driven AI agents with VoltAgent and Airtable using Triggers and Actions.

You'll create an agent that uses Triggers to receive new record events, summarizes them, and uses Actions to write status/next steps back into the same row.

info

Follow the steps with your own base, table, and credential. You can get the agent source code here.




1

Create the Project

Run the CLI to scaffold a new project:

npm create voltagent-app@latest
2

Configure and Start

If you skipped API key entry during setup, create or edit the .env file in your project root:

OPENAI_API_KEY=your-api-key-here

Start the development server:

npm run dev

You should see the VoltAgent server startup message:

═══════════════════════════════════════════════════
VOLTAGENT SERVER STARTED SUCCESSFULLY
═══════════════════════════════════════════════════
✓ HTTP Server: http://localhost:3141
↪ Share it: pnpm volt tunnel 3141 (secure HTTPS tunnel for teammates)
Docs: https://voltagent.dev/docs/deployment/local-tunnel/
✓ Swagger UI: http://localhost:3141/ui

Test your agents with VoltOps Console: https://console.voltagent.dev
═══════════════════════════════════════════════════
3

Set Up the Airtable Trigger in Console



Open VoltAgent Console and go to TriggersCreate Trigger.

  1. Select Airtable → Record created
  2. Select your base and table
  3. Save the trigger
4

Expose Your Local Agent with Volt Tunnel



Volt Tunnel exposes your local server to the internet so triggers can reach it.

Run the tunnel command:

pnpm volt tunnel 3141

Copy the tunnel URL (e.g., https://your-tunnel.tunnel.voltagent.dev) and set it as the Endpoint URL in the trigger configuration.

The project is set up and the Airtable trigger is configured. The following steps cover wiring the trigger to your agent and adding the update action.

5

Wire the Airtable Trigger to Your Agent



This code sets up a trigger handler that receives new Airtable rows and generates field suggestions. The write-back tool is added in the next step.

src/index.ts
import { openai } from "@ai-sdk/openai";
import { Agent, VoltAgent, createTriggers } from "@voltagent/core";
import { createPinoLogger } from "@voltagent/logger";
import { honoServer } from "@voltagent/server-hono";
import { safeStringify } from "@voltagent/internal";

type AirtableRecordCreatedPayload = {
record?: {
id?: string;
fields?: Record<string, unknown>;
};
baseId?: string;
tableId?: string;
};

const logger = createPinoLogger({ name: "with-airtable", level: "info" });

const airtableAgent = new Agent({
name: "airtable-agent",
instructions: `You process newly created Airtable rows.
Draft a summary, a priority (High/Medium/Low), a status (New/In Progress/Blocked/Done), and next steps as bullet text.
You will get a tool to write back in the next step; for now just return the proposed values clearly.`,
model: openai("gpt-4o-mini"),
});

new VoltAgent({
agents: { airtableAgent },
server: honoServer(),
logger,
triggers: createTriggers((on) => {
on.airtable.recordCreated(async ({ payload, agents }) => {
const { record, baseId, tableId } =
(payload as AirtableRecordCreatedPayload | undefined) ?? {};
if (!record?.id) {
logger.warn("Missing recordId in Airtable payload");
return;
}

await agents.airtableAgent.generateText(`Airtable record created.
Base: ${baseId ?? "unknown-base"}
Table: ${tableId ?? "unknown-table"}
Record ID: ${record.id}

Existing fields (JSON): ${safeStringify(record.fields ?? {})}

Propose updates (no tool calls yet):
- Summary (1-2 sentences)
- Priority (High | Medium | Low)
- Status (New | In Progress | Blocked | Done)
- Next steps (short bullet list as a single string)`);
});
}),
});
info

Your Airtable table must include columns named Summary, Priority, Status, and Next steps. Adjust the prompt if your schema differs.

6

Add the Airtable Action and Update Tool



Open VoltAgent Console and go to ActionsCreate Action.

  1. Select Airtable and the same credential
  2. Select Update record, base, and table
  3. Save the action

Add the VoltOps client and updateAirtableRecord tool to your code:

src/index.ts
import { openai } from "@ai-sdk/openai";
import { Agent, VoltAgent, createTool, createTriggers } from "@voltagent/core";
import { VoltOpsClient } from "@voltagent/sdk";
import { createPinoLogger } from "@voltagent/logger";
import { honoServer } from "@voltagent/server-hono";
import { safeStringify } from "@voltagent/internal";
import { z } from "zod";

type AirtableRecordCreatedPayload = {
record?: {
id?: string;
fields?: Record<string, unknown>;
};
baseId?: string;
tableId?: string;
};

const logger = createPinoLogger({ name: "with-airtable", level: "info" });

const voltOps = new VoltOpsClient({
publicKey: process.env.VOLTAGENT_PUBLIC_KEY ?? "",
secretKey: process.env.VOLTAGENT_SECRET_KEY ?? "",
});

const updateAirtableRecord = createTool({
name: "updateAirtableRecord",
description: "Update an Airtable record with summary/priority/status/next steps.",
parameters: z.object({
recordId: z.string(),
fields: z.record(z.unknown()),
baseId: z.string().optional(),
tableId: z.string().optional(),
}),
execute: async ({ recordId, fields, baseId, tableId }) => {
const credentialId = process.env.AIRTABLE_CREDENTIAL_ID;
if (!credentialId) {
throw new Error("AIRTABLE_CREDENTIAL_ID is not set");
}

return voltOps.actions.airtable.updateRecord({
credential: { credentialId },
baseId: baseId,
tableId: tableId,
recordId,
fields,
});
},
});

const airtableAgent = new Agent({
name: "airtable-agent",
instructions: `You process newly created Airtable rows.
Create a short summary, assign a priority (High/Medium/Low), pick a status (New/In Progress/Blocked/Done), and list next steps.
Always write updates via updateAirtableRecord using the exact Airtable field names.`,
tools: [updateAirtableRecord],
model: openai("gpt-4o-mini"),
});

new VoltAgent({
agents: { airtableAgent },
server: honoServer(),
logger,
triggers: createTriggers((on) => {
on.airtable.recordCreated(async ({ payload, agents }) => {
const { record, baseId, tableId } =
(payload as AirtableRecordCreatedPayload | undefined) ?? {};
if (!record?.id) {
logger.warn("Missing recordId in Airtable payload");
return;
}

await agents.airtableAgent.generateText(`Airtable record created.
Base: ${baseId ?? "unknown-base"}
Table: ${tableId ?? "unknown-table"}
Record ID: ${record.id}

Existing fields (JSON): ${safeStringify(record.fields ?? {})}

Update the same record with:
- Summary (1-2 sentences) -> field name: Summary
- Priority (High | Medium | Low) -> field name: Priority
- Status (New | In Progress | Blocked | Done) -> field name: Status
- Next steps (short bullet list as a single string) -> field name: Next steps

Call updateAirtableRecord with recordId and the new fields using those exact names.`);
});
}),
});
7

Test End-to-End



Now test the complete flow from Airtable to your agent and back.

Add these environment variables to your .env file:

VOLTAGENT_PUBLIC_KEY=pk_...
VOLTAGENT_SECRET_KEY=sk_...
AIRTABLE_CREDENTIAL_ID=cred_...

With the tunnel and server running:

  1. Insert a new row in your Airtable table (fill in Title, Description, etc.)
  2. The trigger sends the event to your agent
  3. The agent generates summary/priority/status/next steps
  4. VoltOps writes the fields back to the record

View request/response logs in Actions → Runs in Console.