dc-airtable-cli
v1.0.0
Published
Script-friendly CLI for Airtable record CRUD operations
Readme
Airtable CLI
A script-friendly, production-ready command-line interface for Airtable record CRUD operations, schema inspection, and data management.
Overview
airtable-cli is a TypeScript-based CLI tool that provides safe, efficient access to Airtable's REST API. It's designed for:
- Automation: Build scripts and workflows around Airtable data
- DevOps: Manage data pipelines and integrations
- Testing: Query and manipulate test data
- Batch operations: Create, update, or upsert records at scale
- Schema inspection: Discover table structures and field types
Features
- ✅ CRUD Operations: Create, read, update, delete, and upsert records
- ✅ Flexible Filtering: Use Airtable's powerful
filterByFormulasyntax - ✅ Batch Processing: Handle up to 10 records per request with automatic batching
- ✅ Multiple Output Formats: JSON, tab-separated plain text, or quiet mode
- ✅ Schema Inspection: List tables and fields with type information
- ✅ Configuration Management: Store tokens and base aliases securely
- ✅ Dry-run Mode: Preview changes before executing mutations
- ✅ Type Safety: Written in TypeScript with full type checking
Installation
From NPM
npm install -g airtable-cliFrom Source
git clone <repo>
cd airtable-cli
npm install
npm run build
npm install -g .Quick Start
1. Set Your API Token
Get a Personal Access Token from airtable.com/create/tokens.
# Option 1: Environment variable (recommended)
export AIRTABLE_TOKEN="pat_xxxxx"
# Option 2: Config file
airtable config set token "pat_xxxxx"2. List Records
airtable list <base-id> <table-name>Example:
airtable list appXxxx Contacts3. Create a Record
airtable create <base-id> <table-name> --data '{"Name":"John","Email":"[email protected]"}'4. Query with Filters
airtable list appXxxx Contacts --filter "AND({Status}='Active',{Priority}>5)" --sort Priority --descSee QUICKSTART.md for more examples.
Command Reference
Record Operations
list
List records from a table with filtering and sorting.
airtable list <base> <table> [options]
Options:
-f, --filter <formula> Airtable filterByFormula expression
--fields <fields> Comma-separated field names to return
--sort <field> Field to sort by
--desc Sort descending (default: ascending)
--max <n> Max records to return (default: 100)
--offset <cursor> Pagination cursor for next page
--all Fetch all records automatically (paginate)
--view <name> View name or ID to queryExamples:
# List all contacts
airtable list myBase Contacts
# List active contacts, 50 per page
airtable list myBase Contacts --filter "{Status}='Active'" --max 50
# List and sort by priority
airtable list myBase Contacts --sort Priority --desc
# Fetch everything with automatic pagination
airtable list myBase Contacts --all
# Output as JSON
airtable list myBase Contacts --jsonget
Get a single record by ID.
airtable get <base> <table> <recordId> [options]
Options:
--fields <fields> Comma-separated field names to returnExamples:
# Get record recXxxx
airtable get myBase Contacts recXxxx
# Get specific fields only
airtable get myBase Contacts recXxxx --fields "Name,Email"create
Create one or more records.
airtable create <base> <table> [options]
Options:
-d, --data <json> JSON object or array of records
-f, --file <path> Read JSON from file (use - for stdin)
--typecast Auto-convert field types
--dry-run Preview without creatingExamples:
# Create single record
airtable create myBase Contacts --data '{"Name":"Jane","Email":"[email protected]"}'
# Create multiple records
airtable create myBase Contacts --data '[
{"Name":"Jane","Email":"[email protected]"},
{"Name":"Bob","Email":"[email protected]"}
]'
# Create from file
airtable create myBase Contacts --file records.json
# Create from stdin
cat records.json | airtable create myBase Contacts --file -
# Preview what would be created
airtable create myBase Contacts --file records.json --dry-run
# Auto-convert types (e.g., "99.99" → 99.99)
airtable create myBase Contacts --file records.json --typecastupdate
Update a single record.
airtable update <base> <table> <recordId> [options]
Options:
-d, --data <json> JSON object of fields to update
-f, --file <path> Read JSON from file (use - for stdin)
--typecast Auto-convert field types
--dry-run Preview changes without updatingExamples:
# Update one field
airtable update myBase Contacts recXxxx --data '{"Status":"Active"}'
# Update multiple fields
airtable update myBase Contacts recXxxx --data '{"Status":"Active","Priority":8}'
# Update from file
airtable update myBase Contacts recXxxx --file updates.json
# Preview changes
airtable update myBase Contacts recXxxx --file updates.json --dry-runupsert
Create or update records (merge on specified fields).
airtable upsert <base> <table> --merge-on <fields> [options]
Options:
--merge-on <fields> Comma-separated fields to match on (required)
-d, --data <json> JSON array of records
-f, --file <path> Read JSON from file (use - for stdin)
--typecast Auto-convert field types
--dry-run Preview without upsertingExamples:
# Upsert on Email field
airtable upsert myBase Contacts --merge-on Email --data '[
{"Email":"[email protected]","Name":"John Doe","Status":"Active"},
{"Email":"[email protected]","Name":"Jane Smith","Status":"Pending"}
]'
# Upsert on multiple fields
airtable upsert myBase Contacts --merge-on "Email,Name" --file records.json
# Preview without executing
airtable upsert myBase Contacts --merge-on Email --file records.json --dry-rundelete
Delete one or more records.
airtable delete <base> <table> [recordIds...] [options]
Options:
-f, --file <path> Read record IDs from file (use - for stdin)
--force Skip confirmation (required for non-interactive)
--dry-run Preview without deletingExamples:
# Delete single record
airtable delete myBase Contacts recXxxx --force
# Delete multiple records
airtable delete myBase Contacts recXxxx recYyy recZzz --force
# Delete from file
airtable delete myBase Contacts --file ids.txt --force
# Preview deletions
airtable delete myBase Contacts recXxxx --dry-runSchema Commands
fields
List field names, IDs, and types for a table.
airtable fields <base> <table>Examples:
# List all fields
airtable fields myBase Contacts
# Output as JSON
airtable fields myBase Contacts --json
# Output as plain text
airtable fields myBase Contacts --plaintables
List all tables in a base.
airtable tables <base>Examples:
# List tables
airtable tables myBase
# Output as JSON
airtable tables myBase --jsonConfiguration Commands
config set
Store configuration values.
airtable config set <key> <value>Examples:
# Store API token
airtable config set token "pat_xxxxx"
# Store default base ID
airtable config set defaultBase "appXxxx"config get
Retrieve configuration values.
airtable config get <key>Examples:
airtable config get token
airtable config get defaultBaseconfig list
Show all stored configuration (with token masked for security).
airtable config listalias
Create shortcuts for base IDs.
airtable alias <name> <baseId>Examples:
# Create alias
airtable alias myBase "appXxxx"
# Use alias in commands
airtable list myBase ContactsGlobal Options
These options work with all commands:
--json Output as JSON (default for non-TTY)
--plain Output as tab-separated values
-q, --quiet Suppress non-essential output
--no-color Disable colored output
--token <token> Override AIRTABLE_TOKEN env var
--config <path> Override config file location
--dry-run Preview mutations without executingOutput Formats
JSON Output
airtable list myBase Contacts --jsonReturns structured data:
{
"records": [
{
"id": "recXxxx",
"fields": {
"Name": "John Doe",
"Email": "[email protected]"
}
}
],
"count": 1
}Plain Text Output
airtable list myBase Contacts --plainReturns tab-separated values:
recXxxx John Doe [email protected]
recYyy Jane Smith [email protected]Advanced Usage
Filtering with Formulas
Use Airtable's formula syntax for complex queries:
# Active records with high priority
airtable list myBase Contacts \
--filter "AND({Status}='Active',{Priority}>7)" \
--sort Priority --desc
# Records from the last 30 days
airtable list myBase Contacts \
--filter "IS_AFTER({Created},DATEADD(TODAY(),-30,'days'))"
# Search by text
airtable list myBase Contacts \
--filter "SEARCH('keyword',{Description})>0"
# Exclude archived records
airtable list myBase Contacts \
--filter "NOT({Archived})"Piping and Automation
Chain commands together:
# Export to JSON file
airtable list myBase Contacts --all --json > contacts.json
# Pipe to jq for processing
airtable list myBase Contacts --json | jq '.records[].fields.Email'
# Create from stdin
cat new-contacts.json | airtable create myBase Contacts --file -
# Delete with confirmation
airtable list myBase Contacts --filter "{Status}='Archived'" --json | \
jq -r '.records[].id' | \
xargs airtable delete myBase Contacts --forcePagination
Handle large datasets:
# Get first page (100 records)
airtable list myBase Contacts --max 100
# Get next page using offset
airtable list myBase Contacts --max 100 --offset "itrXxxx"
# Fetch all records with automatic pagination
airtable list myBase Contacts --allBatch Operations
Efficiently handle multiple records:
# Create 50 records (automatically batched in groups of 10)
airtable create myBase Contacts --file large-batch.json
# Upsert with smart merging
airtable upsert myBase Contacts --merge-on Email --file updates.json
# Delete multiple records
airtable delete myBase Contacts rec1 rec2 rec3 rec4 rec5 --forceDry-Run Mode
Preview changes before committing:
# Preview creation
airtable create myBase Contacts --file records.json --dry-run
# Preview updates
airtable update myBase Contacts recXxxx --file changes.json --dry-run
# Preview deletion
airtable delete myBase Contacts recXxxx --dry-run
# Preview upsert
airtable upsert myBase Contacts --merge-on Email --file records.json --dry-runConfiguration Files
Configuration is stored in ~/.config/airtable-cli/config.json:
{
"token": "pat_xxxxx",
"defaultBase": "appXxxx",
"aliases": {
"myBase": "appXxxx",
"staging": "appYyyy"
}
}Precedence
Token resolution order (first match wins):
--tokencommand-line optionAIRTABLE_TOKENenvironment variabletokenin config file- Error: no token found
Base ID resolution order:
- Literal base ID (appXxxx)
- Alias from config (myBase → appXxxx)
defaultBasefrom config- Error: could not resolve base
Error Handling
The CLI provides clear error messages and exit codes:
# Exit codes
0 # Success
1 # General error
2 # Invalid usage
3 # Authentication failure (no token)
4 # Not found (base/table/record)
5 # Rate limit exceeded (429)
6 # Server error (5xx)Authentication
Personal Access Token (Recommended)
- Go to airtable.com/create/tokens
- Click "Create new token"
- Name your token (e.g., "CLI")
- Add scopes:
data.records:read- Read recordsdata.records:write- Create/update records
- Add access to specific bases
- Generate token
- Store securely:
export AIRTABLE_TOKEN="pat_xxxxx" # OR airtable config set token "pat_xxxxx"
API Key (Deprecated)
API keys are deprecated as of February 2024. Use Personal Access Tokens instead.
Rate Limiting
Airtable enforces rate limits:
- Base-level: 5 requests/second
- User-level (PAT): 50 requests/second
- Batch limit: 10 records per request (automatic batching)
The CLI respects these limits. On 429 errors, wait 30 seconds before retrying.
Requirements
- Node.js: 18.0.0 or later
- npm: 8.0.0 or later
- Airtable Account: With Personal Access Token
Development
# Install dependencies
npm install
# Build TypeScript
npm run build
# Watch for changes
npm run dev
# Run tests
npm test
# Type check
npm run typecheck
# Lint
npm run lintProject Structure
src/
├── cli.ts # Main CLI entry point
├── client.ts # Airtable API client
├── config.ts # Configuration management
├── output.ts # Output formatting
├── types.ts # TypeScript type definitions
└── commands/
├── records.ts # Record CRUD commands
├── schema.ts # Schema inspection commands
└── config.ts # Configuration commandsLicense
MIT
Support
For issues, feature requests, or contributions, visit the repository.
Tip: Use airtable --help and airtable <command> --help to see available options for any command.
