Corsair
PluginsAirtable

Airtable

Integrate Airtable bases, tables, and records

Quick Start

Install the plugin:

pnpm install @corsair-dev/airtable

Add the Airtable plugin to your Corsair instance:

corsair.ts
import { createCorsair } from "corsair";
import { airtable } from "@corsair-dev/airtable";

export const corsair = createCorsair({
    plugins: [
        airtable(),
    ],
});

Once configured, you can work with Airtable records:

// Search records in a table
const records = await corsair.airtable.api.records.search({
    baseId: "appXXXXXXXXXXXXXX",
    tableIdOrName: "Tasks",
    filterByFormula: "{Status} = 'In Progress'",
});

Authentication

Supported Auth Types

The Airtable plugin supports:

  • api_key (default) - Use an Airtable personal access token

Default Auth Type

If no authType is specified, the plugin defaults to api_key.

Configuring API Key Authentication

Store credentials with the CLI:

pnpm corsair setup --airtable api_key=your-personal-access-token

For webhook signature verification:

pnpm corsair setup --airtable webhook_signature=your-webhook-secret

Alternatively, provide credentials directly in the config:

corsair.ts
airtable({
    key: process.env.AIRTABLE_API_KEY,
    webhookSecret: process.env.AIRTABLE_WEBHOOK_SECRET,
})

See Authentication for details on managing credentials.

Options

OptionTypeDescription
authType'api_key'Authentication method (defaults to 'api_key')
keystringPersonal access token (optional, uses database if not provided)
webhookSecretstringWebhook secret for x-airtable-content-mac verification
hooksobjectEndpoint hooks for custom logic
webhookHooksobjectWebhook hooks for event handling
errorHandlersobjectCustom error handlers
permissionsobjectPermission configuration for AI agent access

Hooks

airtable({
    hooks: {
        records: {
            create: {
                before: async (ctx, input) => {
                    console.log("Creating record in:", input.tableIdOrName);
                    return { ctx, input };
                },
            },
        },
    },
})

See Hooks for complete documentation.

Error Handling

The plugin includes built-in error handlers for common scenarios. For complete documentation, see the Error Handlers reference.

Usage

Accessing the API

// Create a record
await corsair.airtable.api.records.create({
    baseId: "appXXXXXXXXXXXXXX",
    tableIdOrName: "Tasks",
    fields: {
        Name: "New Task",
        Status: "Todo",
        Assignee: "user@example.com",
    },
});

// Update a record
await corsair.airtable.api.records.update({
    baseId: "appXXXXXXXXXXXXXX",
    tableIdOrName: "Tasks",
    recordId: "recXXXXXXXXXXXXXX",
    fields: { Status: "Done" },
});

// Delete a record
await corsair.airtable.api.records.delete({
    baseId: "appXXXXXXXXXXXXXX",
    tableIdOrName: "Tasks",
    recordId: "recXXXXXXXXXXXXXX",
});

See API Endpoints for the complete reference.

Webhooks

Airtable sends record change events to your webhook endpoint:

app/api/webhook/route.ts
import { processWebhook } from "corsair";
import { corsair } from "@/server/corsair";

export async function POST(request: Request) {
    const headers = Object.fromEntries(request.headers);
    const body = await request.json();

    const result = await processWebhook(corsair, headers, body);
    return result.response;
}

See Webhooks for all available events.

Database Access

const records = await corsair.airtable.db.records.search({
    data: { tableId: "tblXXXXXXXXXXXXXX" },
});

See Database for the complete schema.

Multi-Tenancy

const tenant = corsair.withTenant("org-123");

await tenant.airtable.api.records.create({
    baseId: "appXXXXXXXXXXXXXX",
    tableIdOrName: "CRM",
    fields: { Name: "New Lead", Source: "Website" },
});

Examples

Example 1: Sync Form Submissions to Airtable

inngest/functions.ts
export const syncFormSubmission = inngest.createFunction(
    { id: "sync-form-to-airtable" },
    { event: "app/form-submitted" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);

        await tenant.airtable.api.records.create({
            baseId: process.env.AIRTABLE_BASE_ID!,
            tableIdOrName: "Leads",
            fields: {
                Name: event.data.name, 
                Email: event.data.email, 
                Message: event.data.message, 
                Source: "Web Form",
                Submitted: new Date().toISOString(),
            },
        });
    }
);

Example 2: React to Record Changes

corsair.ts
export const corsair = createCorsair({
    plugins: [
        airtable({
            webhookHooks: {
                events: {
                    event: {
                        after: async (ctx, result) => {
                            await inngest.send({
                                name: "airtable/record-changed",
                                data: {
                                    tenantId: ctx.tenantId,
                                    tableId: result.data.tableId, 
                                    recordId: result.data.recordId,
                                    changeType: result.data.changedFieldsById ? "update" : "create",
                                },
                            });
                        },
                    },
                },
            },
        }),
    ],
});
inngest/functions.ts
export const processAirtableChange = inngest.createFunction(
    { id: "process-airtable-change" },
    { event: "airtable/record-changed" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);

        const record = await tenant.airtable.api.records.get({
            baseId: process.env.AIRTABLE_BASE_ID!,
            tableIdOrName: event.data.tableId,
            recordId: event.data.recordId,
        });

        console.log("Changed record:", record.data.fields);
    }
);

Example 3: Upsert Records on External Events

inngest/functions.ts
export const upsertContact = inngest.createFunction(
    { id: "upsert-airtable-contact" },
    { event: "crm/contact-updated" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);

        await tenant.airtable.api.records.createOrUpdate({
            baseId: process.env.AIRTABLE_BASE_ID!,
            tableIdOrName: "Contacts",
            performUpsert: {
                fieldsToMergeOn: ["Email"], 
            },
            fields: {
                Email: event.data.email,
                Name: event.data.name,
                Company: event.data.company,
                LastUpdated: new Date().toISOString(),
            },
        });
    }
);