If you searched for "how to build an OpenClaw Airtable or Google Sheets skill," you probably already run half your business out of a spreadsheet. Same here. This article walks through a practical, batteries-included skill that lets an OpenClaw agent read and write rows, push automated data, and spit out scheduled reports – all from chat.

The case for a spreadsheet backend

Relational databases are great until the sales team asks for access and you end up exporting CSVs every Friday. Airtable and Google Sheets solve the UI problem but create a new one: manual data entry. OpenClaw closes that loop. An employee pings the agent in Slack – “log new deal ACME Corp $42k” – and the row shows up instantly. Same sheet, no extra permissions, no CRUD web app to maintain.

  • One skill covers both providers – Airtable for users who like rich fields, Sheets for purists.
  • We stick to official SDKs: airtable@^0.12.2 and @googleapis/sheets@^2.0.0.
  • All code runs in Node 22, the version OpenClaw’s runtime ships with.
  • Environment variables hold API keys so nothing leaks to git.

Setup requirements

You need a working OpenClaw workspace (local gateway or ClawCloud), Node 22+, and basic npm hygiene. I’m using openclaw@2.8.1.

  1. Generate an Airtable personal access token with data.records:read and data.records:write.
  2. Create a Google Cloud project, enable the Sheets API, and download either an OAuth client or a service-account JSON. The service account is hassle-free for server tasks.
  3. In your agent folder run: npm i airtable @googleapis/sheets
  4. Add secrets to .env (OpenClaw auto-loads these):
    AIRTABLE_TOKEN=pat123...
    AIRTABLE_BASE=myBaseId
    GOOGLE_SHEETS_CREDS={"type":"service_account",...}"

The Google creds JSON needs to be single-line or you can point to a file path – whatever matches your ops style.

Sketching the skill interface

OpenClaw skills are plain JavaScript modules. Export a manifest describing parameters and a run() function doing the work. Because we want the same user intent – “add row”, “get report” – regardless of backend, we’ll use a provider parameter.

/* skills/spreadsheet.js */ export const manifest = { name: "spreadsheet", description: "Read/write Airtable or Google Sheets rows", parameters: { type: "object", required: ["provider", "action"], properties: { provider: { type: "string", enum: ["airtable", "sheets"] }, action: { type: "string", enum: ["read", "write", "report"] }, table: { type: "string", description: "Airtable table or sheet name" }, data: { type: "object", description: "Row contents for writes" } } } }; export async function run({ provider, action, table, data }) { if (provider === "airtable") return airtableHandler({ action, table, data }); if (provider === "sheets") return sheetsHandler({ action, table, data }); throw new Error(`Unsupported provider ${provider}`); }

The two private helpers – airtableHandler and sheetsHandler – hold the provider-specific code so the top layer stays clean.

Airtable integration

Client setup

import Airtable from 'airtable'; const airtable = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN }) .base(process.env.AIRTABLE_BASE);

Keep the base id in .env; you don’t want to hard-code it because bases move between workspaces.

Reading rows from Airtable

async function airtableRead(table) { const records = await airtable(table).select({ view: 'Grid view' }).all(); return records.map(r => ({ id: r.id, ...r.fields })); }

The SDK returns Airtable’s weird field format. I flatten it so the LLM doesn’t have to parse nested objects.

Writing rows to Airtable

async function airtableWrite(table, data) { const res = await airtable(table).create([{ fields: data }]); return { id: res[0].id }; }

Airtable restricts 10 rows per call on free plans. If your agent is likely to spam, batch the writes but keep under that cap.

Glue code

async function airtableHandler({ action, table, data }) { if (action === 'read') return airtableRead(table); if (action === 'write') return airtableWrite(table, data); if (action === 'report') return airtableReport(table); throw new Error('Unknown action'); }

airtableReport can be as fancy as you like; a quick version just aggregates numeric columns and returns JSON for the agent to narrate.

Google Sheets integration

Bootstrapping the Sheets API

import { google } from '@googleapis/sheets'; const auth = new google.auth.GoogleAuth({ credentials: JSON.parse(process.env.GOOGLE_SHEETS_CREDS), scopes: ['https://www.googleapis.com/auth/spreadsheets'] }); const sheets = google.sheets({ version: 'v4', auth }); const SPREADSHEET_ID = process.env.GOOGLE_SHEETS_ID;

Unlike Airtable, Sheets separates the spreadsheet (file) from the individual tab. We keep SPREADSHEET_ID in env and pass the tab name on each call.

Reading rows

async function sheetsRead(tab) { const { data } = await sheets.spreadsheets.values.get({ spreadsheetId: SPREADSHEET_ID, range: `${tab}!A1:Z`, }); const [header, ...rows] = data.values; return rows.map(r => Object.fromEntries(header.map((h, i) => [h, r[i] || '']))); }

I pull the header row to map columns – that keeps things robust if someone reorders columns Monday morning.

Writing rows

async function sheetsWrite(tab, data) { const keys = Object.keys(data); const values = [keys.map(k => data[k])]; await sheets.spreadsheets.values.append({ spreadsheetId: SPREADSHEET_ID, range: `${tab}!A1`, valueInputOption: 'USER_ENTERED', requestBody: { values } }); return { ok: true }; }

Sheets doesn’t auto-create headers. Make sure the first row already contains them or append will just dump values into A1.

Put it together

async function sheetsHandler({ action, table, data }) { if (action === 'read') return sheetsRead(table); if (action === 'write') return sheetsWrite(table, data); if (action === 'report') return sheetsReport(table); }

Again the report task can group by status, sum amounts, whatever your business cares about.

Wiring the skill into chat workflows

With the skill file in skills/, OpenClaw autoloads it at startup. The magic is in the prompt template you give your agent. Mine lives in agent.yml:

name: OpsBot skills: - spreadsheet prompt: | You help the team manipulate the "Deals" base in Airtable and the "Daily KPIs" sheet in Google Sheets. Use the spreadsheet skill when someone asks to read, write, or report.

Because the manifest includes a JSON schema, the LLM knows the required fields. Still, I like to show the model an explicit example:

## Example User: log new deal ACME 42000 Assistant (internal): { "provider": "airtable", "action": "write", "table": "Deals", "data": { "Name": "ACME", "Amount": 42000 } }

The gateway hides the JSON from the end user; they see a friendly confirmation message.

Scheduling reports and production deployment

OpenClaw supports CRON-style schedules via the daemon. Create tasks/deals-report.mjs:

export const cron = '0 9 * * 1-5'; // 09:00 on weekdays export async function run() { const report = await global.skills.spreadsheet.run({ provider: 'airtable', action: 'report', table: 'Deals' }); await global.chat.post('sales-channel', format(report)); }

Deploy to ClawCloud with:

git push clawcloud main

The build image already has Node 22 and your package.json scripts run automatically. Secrets are injected from the ClawCloud dashboard.

First run will appear in the dashboard logs; if the Sheets client complains about auth scopes, double-check the service account has shared access to the spreadsheet file.

Putting it to work: chat-driven ops dashboard

Once the skill is live, the team can:

  • Add deals: “/bot deal Globex 77k” – goes to Airtable
  • Get pipeline: “/bot pipeline summary” – agent reads Airtable and posts totals
  • Update KPIs: CI job hits the skill’s HTTP endpoint to append yesterday’s metrics to Sheets
  • Morning digest: 9 AM task posts a Sheets-generated chart into Slack

We’ve used this setup for three months. The biggest win is non-technical teammates never ask for database access; they already have it in spreadsheet form. Downside: Google quotas (500 writes/min) bite faster than you’d expect, so batch high-volume jobs.

Next step: add validation. I keep a columns.json with allowed field names and sane defaults. The skill checks that before writing to avoid silent spreadsheet chaos.

That’s it – a single skill, ~150 lines, and your agent now drives Airtable and Google Sheets like any other API.