chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: database
|
|
3
|
+
description: "Airtable JavaScript SDK (airtable.js) — use the official airtable npm package for Airtable API operations"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "0.12.2"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "airtable,database,low-code,spreadsheet,api"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Airtable JavaScript SDK (airtable.js) - Version 0.12.2
|
|
13
|
+
|
|
14
|
+
## Golden Rule
|
|
15
|
+
|
|
16
|
+
**ALWAYS use the official `airtable` npm package (version 0.12.2 or later)**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install airtable
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**DO NOT use:**
|
|
23
|
+
- Deprecated or unofficial packages like `airtable-node`, `airtable-plus`, or other third-party wrappers
|
|
24
|
+
- The old API key authentication method (deprecated as of February 1, 2024)
|
|
25
|
+
- The `@airtable/blocks` package unless building Airtable extensions
|
|
26
|
+
|
|
27
|
+
**ALWAYS use Personal Access Tokens (PATs) for authentication**, not the deprecated API keys.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install airtable
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Environment Variable Setup
|
|
36
|
+
|
|
37
|
+
Create a `.env` file:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
AIRTABLE_API_KEY=your_personal_access_token_here
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For production applications, use proper secret management systems.
|
|
44
|
+
|
|
45
|
+
## Authentication & Initialization
|
|
46
|
+
|
|
47
|
+
### Personal Access Token Setup
|
|
48
|
+
|
|
49
|
+
1. Visit https://airtable.com/create/tokens to create a Personal Access Token
|
|
50
|
+
2. Name your token (e.g., "My App Token")
|
|
51
|
+
3. Add required scopes:
|
|
52
|
+
- `data.records:read` - to read records
|
|
53
|
+
- `data.records:write` - to create/update/delete records
|
|
54
|
+
- `schema.bases:read` - to read base structure (optional)
|
|
55
|
+
4. Select base access level (specific bases or all workspace bases)
|
|
56
|
+
5. Copy the token immediately (shown only once)
|
|
57
|
+
|
|
58
|
+
### Basic Configuration
|
|
59
|
+
|
|
60
|
+
**Option 1: Global Configuration**
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const Airtable = require('airtable');
|
|
64
|
+
|
|
65
|
+
Airtable.configure({
|
|
66
|
+
apiKey: 'your_personal_access_token'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const base = Airtable.base('appYourBaseId');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Option 2: Environment Variable**
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const Airtable = require('airtable');
|
|
76
|
+
|
|
77
|
+
// Automatically reads from AIRTABLE_API_KEY environment variable
|
|
78
|
+
const base = Airtable.base('appYourBaseId');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Option 3: Per-Instance Configuration**
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
const Airtable = require('airtable');
|
|
85
|
+
|
|
86
|
+
const airtable = new Airtable({
|
|
87
|
+
apiKey: process.env.AIRTABLE_API_KEY
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const base = airtable.base('appYourBaseId');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Advanced Configuration Options
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const Airtable = require('airtable');
|
|
97
|
+
|
|
98
|
+
Airtable.configure({
|
|
99
|
+
apiKey: process.env.AIRTABLE_API_KEY,
|
|
100
|
+
endpointUrl: 'https://api.airtable.com', // Custom endpoint (optional)
|
|
101
|
+
requestTimeout: 300000 // Request timeout in milliseconds (default: 300000)
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Finding Your Base and Table IDs
|
|
106
|
+
|
|
107
|
+
1. Go to https://airtable.com/api
|
|
108
|
+
2. Select your base
|
|
109
|
+
3. Base ID format: `appXXXXXXXXXXXXXX`
|
|
110
|
+
4. Table names are case-sensitive strings (e.g., 'Tasks', 'Contacts')
|
|
111
|
+
|
|
112
|
+
## Core API Surfaces
|
|
113
|
+
|
|
114
|
+
### Reading Records
|
|
115
|
+
|
|
116
|
+
#### Get Single Record by ID
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const table = base('Tasks');
|
|
120
|
+
|
|
121
|
+
// Callback style
|
|
122
|
+
table.find('recXXXXXXXXXXXXXX', function(err, record) {
|
|
123
|
+
if (err) {
|
|
124
|
+
console.error(err);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
console.log('Retrieved', record.get('Name'));
|
|
128
|
+
console.log('Record ID:', record.id);
|
|
129
|
+
console.log('Created time:', record._rawJson.createdTime);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Promise style
|
|
133
|
+
table.find('recXXXXXXXXXXXXXX')
|
|
134
|
+
.then(record => {
|
|
135
|
+
console.log('Retrieved', record.get('Name'));
|
|
136
|
+
})
|
|
137
|
+
.catch(err => {
|
|
138
|
+
console.error(err);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Async/await style
|
|
142
|
+
async function getRecord() {
|
|
143
|
+
try {
|
|
144
|
+
const record = await table.find('recXXXXXXXXXXXXXX');
|
|
145
|
+
console.log('Name:', record.get('Name'));
|
|
146
|
+
console.log('Status:', record.get('Status'));
|
|
147
|
+
|
|
148
|
+
// Access all fields
|
|
149
|
+
const fields = record.fields;
|
|
150
|
+
console.log('All fields:', fields);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error(err);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Get All Records (≤100 records)
|
|
158
|
+
|
|
159
|
+
Use `firstPage()` when you know your table has 100 or fewer records:
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
const table = base('Tasks');
|
|
163
|
+
|
|
164
|
+
// Callback style
|
|
165
|
+
table.select({
|
|
166
|
+
view: 'Grid view'
|
|
167
|
+
}).firstPage(function(err, records) {
|
|
168
|
+
if (err) {
|
|
169
|
+
console.error(err);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
records.forEach(function(record) {
|
|
174
|
+
console.log('Retrieved', record.get('Name'));
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Promise style
|
|
179
|
+
table.select().firstPage()
|
|
180
|
+
.then(records => {
|
|
181
|
+
records.forEach(record => {
|
|
182
|
+
console.log('Name:', record.get('Name'));
|
|
183
|
+
console.log('Status:', record.get('Status'));
|
|
184
|
+
});
|
|
185
|
+
})
|
|
186
|
+
.catch(err => {
|
|
187
|
+
console.error(err);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Async/await style
|
|
191
|
+
async function getAllRecords() {
|
|
192
|
+
try {
|
|
193
|
+
const records = await table.select().firstPage();
|
|
194
|
+
|
|
195
|
+
records.forEach(record => {
|
|
196
|
+
console.log('ID:', record.id);
|
|
197
|
+
console.log('Name:', record.get('Name'));
|
|
198
|
+
});
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error(err);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Paginated Reading (>100 records)
|
|
206
|
+
|
|
207
|
+
Use `eachPage()` for tables with more than 100 records:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
const table = base('Tasks');
|
|
211
|
+
|
|
212
|
+
let allRecords = [];
|
|
213
|
+
|
|
214
|
+
table.select({
|
|
215
|
+
view: 'Grid view'
|
|
216
|
+
}).eachPage(
|
|
217
|
+
function page(records, fetchNextPage) {
|
|
218
|
+
// Called for each page of records
|
|
219
|
+
records.forEach(function(record) {
|
|
220
|
+
console.log('Retrieved', record.get('Name'));
|
|
221
|
+
allRecords.push(record);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Fetch the next page of records
|
|
225
|
+
fetchNextPage();
|
|
226
|
+
},
|
|
227
|
+
function done(err) {
|
|
228
|
+
// Called when all pages have been retrieved
|
|
229
|
+
if (err) {
|
|
230
|
+
console.error(err);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log(`Total records: ${allRecords.length}`);
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Using all() Method with Async/Await
|
|
240
|
+
|
|
241
|
+
The `all()` method retrieves all pages synchronously:
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
const table = base('Tasks');
|
|
245
|
+
|
|
246
|
+
async function getAllRecordsSimple() {
|
|
247
|
+
try {
|
|
248
|
+
const records = await table.select({
|
|
249
|
+
view: 'Grid view'
|
|
250
|
+
}).all();
|
|
251
|
+
|
|
252
|
+
console.log(`Total records: ${records.length}`);
|
|
253
|
+
|
|
254
|
+
records.forEach(record => {
|
|
255
|
+
console.log('Name:', record.get('Name'));
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return records;
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error(err);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Important:** Only use `all()` when you expect a manageable number of records. For very large tables, use `eachPage()` to avoid memory issues.
|
|
266
|
+
|
|
267
|
+
### Filtering Records
|
|
268
|
+
|
|
269
|
+
#### Using filterByFormula
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
const table = base('Tasks');
|
|
273
|
+
|
|
274
|
+
// Simple filter
|
|
275
|
+
const records = await table.select({
|
|
276
|
+
filterByFormula: "{Status} = 'Active'"
|
|
277
|
+
}).all();
|
|
278
|
+
|
|
279
|
+
// Multiple conditions with AND
|
|
280
|
+
const activeHighPriority = await table.select({
|
|
281
|
+
filterByFormula: "AND({Status} = 'Active', {Priority} = 'High')"
|
|
282
|
+
}).all();
|
|
283
|
+
|
|
284
|
+
// Multiple conditions with OR
|
|
285
|
+
const urgentOrBlocked = await table.select({
|
|
286
|
+
filterByFormula: "OR({Status} = 'Urgent', {Status} = 'Blocked')"
|
|
287
|
+
}).all();
|
|
288
|
+
|
|
289
|
+
// Not empty check
|
|
290
|
+
const withTitles = await table.select({
|
|
291
|
+
filterByFormula: "NOT({Title} = '')"
|
|
292
|
+
}).all();
|
|
293
|
+
|
|
294
|
+
// Greater than comparison
|
|
295
|
+
const recentTasks = await table.select({
|
|
296
|
+
filterByFormula: "{Created} > '2025-01-01'"
|
|
297
|
+
}).all();
|
|
298
|
+
|
|
299
|
+
// String matching with variable
|
|
300
|
+
const email = 'user@example.com';
|
|
301
|
+
const userRecords = await table.select({
|
|
302
|
+
filterByFormula: `{Email} = "${email}"`
|
|
303
|
+
}).all();
|
|
304
|
+
|
|
305
|
+
// Complex formula
|
|
306
|
+
const complexFilter = await table.select({
|
|
307
|
+
filterByFormula: `AND(
|
|
308
|
+
{Status} = 'In Progress',
|
|
309
|
+
{Priority} = 'High',
|
|
310
|
+
{Assignee} != '',
|
|
311
|
+
{DueDate} <= TODAY()
|
|
312
|
+
)`
|
|
313
|
+
}).all();
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Sorting Records
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
const table = base('Tasks');
|
|
320
|
+
|
|
321
|
+
// Sort by single field (ascending)
|
|
322
|
+
const sortedAsc = await table.select({
|
|
323
|
+
sort: [{field: 'Name', direction: 'asc'}]
|
|
324
|
+
}).all();
|
|
325
|
+
|
|
326
|
+
// Sort by single field (descending)
|
|
327
|
+
const sortedDesc = await table.select({
|
|
328
|
+
sort: [{field: 'Created', direction: 'desc'}]
|
|
329
|
+
}).all();
|
|
330
|
+
|
|
331
|
+
// Sort by multiple fields
|
|
332
|
+
const multiSort = await table.select({
|
|
333
|
+
sort: [
|
|
334
|
+
{field: 'Priority', direction: 'desc'},
|
|
335
|
+
{field: 'DueDate', direction: 'asc'},
|
|
336
|
+
{field: 'Name', direction: 'asc'}
|
|
337
|
+
]
|
|
338
|
+
}).all();
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Limiting and Pagination Parameters
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
const table = base('Tasks');
|
|
345
|
+
|
|
346
|
+
// Limit total number of records returned
|
|
347
|
+
const limitedRecords = await table.select({
|
|
348
|
+
maxRecords: 50
|
|
349
|
+
}).all();
|
|
350
|
+
|
|
351
|
+
// Set page size (max 100)
|
|
352
|
+
const customPageSize = await table.select({
|
|
353
|
+
pageSize: 50
|
|
354
|
+
}).firstPage();
|
|
355
|
+
|
|
356
|
+
// Combine all parameters
|
|
357
|
+
const complexQuery = await table.select({
|
|
358
|
+
view: 'Active Tasks',
|
|
359
|
+
maxRecords: 100,
|
|
360
|
+
pageSize: 50,
|
|
361
|
+
filterByFormula: "NOT({Name} = '')",
|
|
362
|
+
sort: [{field: 'Priority', direction: 'desc'}]
|
|
363
|
+
}).all();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Selecting Specific Fields
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
const table = base('Tasks');
|
|
370
|
+
|
|
371
|
+
// Only retrieve specific fields
|
|
372
|
+
const records = await table.select({
|
|
373
|
+
fields: ['Name', 'Status', 'Assignee']
|
|
374
|
+
}).all();
|
|
375
|
+
|
|
376
|
+
records.forEach(record => {
|
|
377
|
+
console.log('Name:', record.get('Name'));
|
|
378
|
+
console.log('Status:', record.get('Status'));
|
|
379
|
+
// Other fields will be undefined
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Using Views
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
const table = base('Tasks');
|
|
387
|
+
|
|
388
|
+
// Select from a specific view
|
|
389
|
+
const viewRecords = await table.select({
|
|
390
|
+
view: 'Active Tasks'
|
|
391
|
+
}).all();
|
|
392
|
+
|
|
393
|
+
// Combine view with other parameters
|
|
394
|
+
const filteredView = await table.select({
|
|
395
|
+
view: 'Active Tasks',
|
|
396
|
+
filterByFormula: "{Priority} = 'High'",
|
|
397
|
+
sort: [{field: 'DueDate', direction: 'asc'}]
|
|
398
|
+
}).all();
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Creating Records
|
|
402
|
+
|
|
403
|
+
#### Create Single Record
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
const table = base('Tasks');
|
|
407
|
+
|
|
408
|
+
// Callback style
|
|
409
|
+
table.create({
|
|
410
|
+
'Name': 'New Task',
|
|
411
|
+
'Status': 'To Do',
|
|
412
|
+
'Priority': 'Medium'
|
|
413
|
+
}, function(err, record) {
|
|
414
|
+
if (err) {
|
|
415
|
+
console.error(err);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
console.log('Created record:', record.id);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Promise style
|
|
422
|
+
table.create({
|
|
423
|
+
'Name': 'New Task',
|
|
424
|
+
'Status': 'To Do'
|
|
425
|
+
}).then(record => {
|
|
426
|
+
console.log('Created record:', record.id);
|
|
427
|
+
}).catch(err => {
|
|
428
|
+
console.error(err);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Async/await style
|
|
432
|
+
async function createTask() {
|
|
433
|
+
try {
|
|
434
|
+
const record = await table.create({
|
|
435
|
+
'Name': 'New Task',
|
|
436
|
+
'Status': 'To Do',
|
|
437
|
+
'Priority': 'High',
|
|
438
|
+
'Notes': 'This is a new task'
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
console.log('Created record:', record.id);
|
|
442
|
+
console.log('Name:', record.get('Name'));
|
|
443
|
+
|
|
444
|
+
return record;
|
|
445
|
+
} catch (err) {
|
|
446
|
+
console.error(err);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
#### Create with Typecast
|
|
452
|
+
|
|
453
|
+
```javascript
|
|
454
|
+
const table = base('Tasks');
|
|
455
|
+
|
|
456
|
+
// Typecast converts strings to appropriate types
|
|
457
|
+
const record = await table.create({
|
|
458
|
+
'Name': 'Task with Date',
|
|
459
|
+
'DueDate': '2025-12-31', // String will be converted to date
|
|
460
|
+
'Count': '42' // String will be converted to number
|
|
461
|
+
}, {
|
|
462
|
+
typecast: true
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
#### Batch Create (Multiple Records)
|
|
467
|
+
|
|
468
|
+
Airtable allows up to 10 records per batch operation:
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
const table = base('Tasks');
|
|
472
|
+
|
|
473
|
+
// Callback style
|
|
474
|
+
table.create([
|
|
475
|
+
{
|
|
476
|
+
'Name': 'Task 1',
|
|
477
|
+
'Status': 'To Do'
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
'Name': 'Task 2',
|
|
481
|
+
'Status': 'In Progress'
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
'Name': 'Task 3',
|
|
485
|
+
'Status': 'Done'
|
|
486
|
+
}
|
|
487
|
+
], function(err, records) {
|
|
488
|
+
if (err) {
|
|
489
|
+
console.error(err);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
records.forEach(function(record) {
|
|
494
|
+
console.log('Created:', record.id);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Async/await style
|
|
499
|
+
async function batchCreate() {
|
|
500
|
+
try {
|
|
501
|
+
const records = await table.create([
|
|
502
|
+
{'Name': 'Task 1', 'Status': 'To Do'},
|
|
503
|
+
{'Name': 'Task 2', 'Status': 'In Progress'},
|
|
504
|
+
{'Name': 'Task 3', 'Status': 'Done'},
|
|
505
|
+
{'Name': 'Task 4', 'Status': 'To Do'},
|
|
506
|
+
{'Name': 'Task 5', 'Status': 'Review'}
|
|
507
|
+
]);
|
|
508
|
+
|
|
509
|
+
console.log(`Created ${records.length} records`);
|
|
510
|
+
|
|
511
|
+
return records;
|
|
512
|
+
} catch (err) {
|
|
513
|
+
console.error(err);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// With typecast
|
|
518
|
+
const recordsWithTypecast = await table.create([
|
|
519
|
+
{'Name': 'Task 1', 'Count': '10'},
|
|
520
|
+
{'Name': 'Task 2', 'Count': '20'}
|
|
521
|
+
], {
|
|
522
|
+
typecast: true
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Updating Records
|
|
527
|
+
|
|
528
|
+
#### Update Single Record (Partial Update)
|
|
529
|
+
|
|
530
|
+
```javascript
|
|
531
|
+
const table = base('Tasks');
|
|
532
|
+
|
|
533
|
+
// Callback style
|
|
534
|
+
table.update('recXXXXXXXXXXXXXX', {
|
|
535
|
+
'Status': 'In Progress',
|
|
536
|
+
'Notes': 'Updated notes'
|
|
537
|
+
}, function(err, record) {
|
|
538
|
+
if (err) {
|
|
539
|
+
console.error(err);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
console.log('Updated record:', record.id);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Promise style
|
|
546
|
+
table.update('recXXXXXXXXXXXXXX', {
|
|
547
|
+
'Status': 'Done'
|
|
548
|
+
}).then(record => {
|
|
549
|
+
console.log('Updated:', record.get('Status'));
|
|
550
|
+
}).catch(err => {
|
|
551
|
+
console.error(err);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Async/await style
|
|
555
|
+
async function updateTask(recordId) {
|
|
556
|
+
try {
|
|
557
|
+
const record = await table.update(recordId, {
|
|
558
|
+
'Status': 'In Progress',
|
|
559
|
+
'Progress': 50
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
console.log('Updated record:', record.id);
|
|
563
|
+
console.log('New status:', record.get('Status'));
|
|
564
|
+
|
|
565
|
+
return record;
|
|
566
|
+
} catch (err) {
|
|
567
|
+
console.error(err);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### Replace Record (Full Update)
|
|
573
|
+
|
|
574
|
+
The `replace()` method replaces all fields (fields not included will be cleared):
|
|
575
|
+
|
|
576
|
+
```javascript
|
|
577
|
+
const table = base('Tasks');
|
|
578
|
+
|
|
579
|
+
// Replace clears fields not specified
|
|
580
|
+
const record = await table.replace('recXXXXXXXXXXXXXX', {
|
|
581
|
+
'Name': 'Completely New Task',
|
|
582
|
+
'Status': 'To Do'
|
|
583
|
+
// All other fields will be cleared
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
#### Batch Update (Multiple Records)
|
|
588
|
+
|
|
589
|
+
```javascript
|
|
590
|
+
const table = base('Tasks');
|
|
591
|
+
|
|
592
|
+
// Update up to 10 records at once
|
|
593
|
+
const records = await table.update([
|
|
594
|
+
{
|
|
595
|
+
id: 'recXXXXXXXXXXXXXX',
|
|
596
|
+
fields: {
|
|
597
|
+
'Status': 'Done'
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: 'recYYYYYYYYYYYYYY',
|
|
602
|
+
fields: {
|
|
603
|
+
'Status': 'In Progress',
|
|
604
|
+
'Progress': 75
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
console.log(`Updated ${records.length} records`);
|
|
610
|
+
|
|
611
|
+
// With typecast
|
|
612
|
+
const updatedWithTypecast = await table.update([
|
|
613
|
+
{
|
|
614
|
+
id: 'recXXXXXXXXXXXXXX',
|
|
615
|
+
fields: {'Count': '100'}
|
|
616
|
+
}
|
|
617
|
+
], {
|
|
618
|
+
typecast: true
|
|
619
|
+
});
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### Batch Replace (Multiple Records)
|
|
623
|
+
|
|
624
|
+
```javascript
|
|
625
|
+
const table = base('Tasks');
|
|
626
|
+
|
|
627
|
+
// Replace up to 10 records at once
|
|
628
|
+
const records = await table.replace([
|
|
629
|
+
{
|
|
630
|
+
id: 'recXXXXXXXXXXXXXX',
|
|
631
|
+
fields: {
|
|
632
|
+
'Name': 'New Name',
|
|
633
|
+
'Status': 'To Do'
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: 'recYYYYYYYYYYYYYY',
|
|
638
|
+
fields: {
|
|
639
|
+
'Name': 'Another Task',
|
|
640
|
+
'Status': 'Done'
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
]);
|
|
644
|
+
|
|
645
|
+
// With typecast
|
|
646
|
+
const replacedWithTypecast = await table.replace([
|
|
647
|
+
{
|
|
648
|
+
id: 'recXXXXXXXXXXXXXX',
|
|
649
|
+
fields: {'Name': 'Task', 'Count': '50'}
|
|
650
|
+
}
|
|
651
|
+
], {
|
|
652
|
+
typecast: true
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Deleting Records
|
|
657
|
+
|
|
658
|
+
#### Delete Single Record
|
|
659
|
+
|
|
660
|
+
```javascript
|
|
661
|
+
const table = base('Tasks');
|
|
662
|
+
|
|
663
|
+
// Callback style
|
|
664
|
+
table.destroy('recXXXXXXXXXXXXXX', function(err, deletedRecord) {
|
|
665
|
+
if (err) {
|
|
666
|
+
console.error(err);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
console.log('Deleted record:', deletedRecord.id);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// Promise style
|
|
673
|
+
table.destroy('recXXXXXXXXXXXXXX')
|
|
674
|
+
.then(deletedRecord => {
|
|
675
|
+
console.log('Deleted:', deletedRecord.id);
|
|
676
|
+
})
|
|
677
|
+
.catch(err => {
|
|
678
|
+
console.error(err);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Async/await style
|
|
682
|
+
async function deleteTask(recordId) {
|
|
683
|
+
try {
|
|
684
|
+
const deletedRecord = await table.destroy(recordId);
|
|
685
|
+
console.log('Deleted record:', deletedRecord.id);
|
|
686
|
+
|
|
687
|
+
return deletedRecord;
|
|
688
|
+
} catch (err) {
|
|
689
|
+
console.error(err);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### Batch Delete (Multiple Records)
|
|
695
|
+
|
|
696
|
+
```javascript
|
|
697
|
+
const table = base('Tasks');
|
|
698
|
+
|
|
699
|
+
// Delete up to 10 records at once
|
|
700
|
+
// Callback style
|
|
701
|
+
table.destroy([
|
|
702
|
+
'recXXXXXXXXXXXXXX',
|
|
703
|
+
'recYYYYYYYYYYYYYY',
|
|
704
|
+
'recZZZZZZZZZZZZZZ'
|
|
705
|
+
], function(err, deletedRecords) {
|
|
706
|
+
if (err) {
|
|
707
|
+
console.error(err);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
console.log('Deleted', deletedRecords.length, 'records');
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Async/await style
|
|
714
|
+
async function batchDelete(recordIds) {
|
|
715
|
+
try {
|
|
716
|
+
const deletedRecords = await table.destroy(recordIds);
|
|
717
|
+
console.log(`Deleted ${deletedRecords.length} records`);
|
|
718
|
+
|
|
719
|
+
deletedRecords.forEach(record => {
|
|
720
|
+
console.log('Deleted:', record.id);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
return deletedRecords;
|
|
724
|
+
} catch (err) {
|
|
725
|
+
console.error(err);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Example usage
|
|
730
|
+
await batchDelete([
|
|
731
|
+
'recXXXXXXXXXXXXXX',
|
|
732
|
+
'recYYYYYYYYYYYYYY'
|
|
733
|
+
]);
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Working with Different Field Types
|
|
737
|
+
|
|
738
|
+
#### Text Fields
|
|
739
|
+
|
|
740
|
+
```javascript
|
|
741
|
+
const record = await table.create({
|
|
742
|
+
'Single Line Text': 'Short text',
|
|
743
|
+
'Long Text': 'This is a much longer text\nwith multiple lines',
|
|
744
|
+
'Email': 'user@example.com',
|
|
745
|
+
'URL': 'https://example.com',
|
|
746
|
+
'Phone': '+1-555-0100'
|
|
747
|
+
});
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### Number Fields
|
|
751
|
+
|
|
752
|
+
```javascript
|
|
753
|
+
const record = await table.create({
|
|
754
|
+
'Number': 42,
|
|
755
|
+
'Currency': 99.99,
|
|
756
|
+
'Percent': 0.75,
|
|
757
|
+
'Rating': 5
|
|
758
|
+
});
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
#### Date and Time Fields
|
|
762
|
+
|
|
763
|
+
```javascript
|
|
764
|
+
const record = await table.create({
|
|
765
|
+
'Date': '2025-10-25',
|
|
766
|
+
'DateTime': '2025-10-25T14:30:00.000Z'
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
// Access date fields
|
|
770
|
+
const dateValue = record.get('Date');
|
|
771
|
+
console.log('Date:', dateValue);
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
#### Checkbox (Boolean) Fields
|
|
775
|
+
|
|
776
|
+
```javascript
|
|
777
|
+
const record = await table.create({
|
|
778
|
+
'Completed': true,
|
|
779
|
+
'Active': false
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Access checkbox
|
|
783
|
+
const isCompleted = record.get('Completed');
|
|
784
|
+
if (isCompleted) {
|
|
785
|
+
console.log('Task is completed');
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
#### Single Select Fields
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
const record = await table.create({
|
|
793
|
+
'Status': 'In Progress', // Must match exact option name
|
|
794
|
+
'Priority': 'High'
|
|
795
|
+
});
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
#### Multiple Select Fields
|
|
799
|
+
|
|
800
|
+
```javascript
|
|
801
|
+
const record = await table.create({
|
|
802
|
+
'Tags': ['Important', 'Urgent', 'Client Work']
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Access multiple select
|
|
806
|
+
const tags = record.get('Tags');
|
|
807
|
+
console.log('Tags:', tags.join(', '));
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
#### Attachment Fields
|
|
811
|
+
|
|
812
|
+
```javascript
|
|
813
|
+
const record = await table.create({
|
|
814
|
+
'Attachments': [
|
|
815
|
+
{
|
|
816
|
+
url: 'https://example.com/image.jpg'
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
url: 'https://example.com/document.pdf'
|
|
820
|
+
}
|
|
821
|
+
]
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// Access attachments
|
|
825
|
+
const attachments = record.get('Attachments');
|
|
826
|
+
attachments.forEach(attachment => {
|
|
827
|
+
console.log('File:', attachment.filename);
|
|
828
|
+
console.log('URL:', attachment.url);
|
|
829
|
+
console.log('Size:', attachment.size);
|
|
830
|
+
console.log('Type:', attachment.type);
|
|
831
|
+
});
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
#### Linked Record Fields
|
|
835
|
+
|
|
836
|
+
```javascript
|
|
837
|
+
// Link to existing records by their IDs
|
|
838
|
+
const record = await table.create({
|
|
839
|
+
'Linked Records': [
|
|
840
|
+
'recXXXXXXXXXXXXXX',
|
|
841
|
+
'recYYYYYYYYYYYYYY'
|
|
842
|
+
]
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// Access linked records
|
|
846
|
+
const linkedRecords = record.get('Linked Records');
|
|
847
|
+
console.log('Linked record IDs:', linkedRecords);
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
#### Collaborator Fields
|
|
851
|
+
|
|
852
|
+
```javascript
|
|
853
|
+
const record = await table.create({
|
|
854
|
+
'Assignee': {
|
|
855
|
+
id: 'usrXXXXXXXXXXXXXX',
|
|
856
|
+
email: 'user@example.com'
|
|
857
|
+
},
|
|
858
|
+
'Collaborators': [
|
|
859
|
+
{id: 'usrXXXXXXXXXXXXXX'},
|
|
860
|
+
{id: 'usrYYYYYYYYYYYYYY'}
|
|
861
|
+
]
|
|
862
|
+
});
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
## Complete Examples
|
|
866
|
+
|
|
867
|
+
### Example 1: Task Management System
|
|
868
|
+
|
|
869
|
+
```javascript
|
|
870
|
+
const Airtable = require('airtable');
|
|
871
|
+
|
|
872
|
+
Airtable.configure({
|
|
873
|
+
apiKey: process.env.AIRTABLE_API_KEY
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
const base = Airtable.base('appTaskManager');
|
|
877
|
+
const tasksTable = base('Tasks');
|
|
878
|
+
|
|
879
|
+
// Create a new task
|
|
880
|
+
async function createTask(taskData) {
|
|
881
|
+
try {
|
|
882
|
+
const record = await tasksTable.create({
|
|
883
|
+
'Name': taskData.name,
|
|
884
|
+
'Description': taskData.description,
|
|
885
|
+
'Status': 'To Do',
|
|
886
|
+
'Priority': taskData.priority || 'Medium',
|
|
887
|
+
'Assignee': taskData.assignee,
|
|
888
|
+
'Due Date': taskData.dueDate
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
console.log('Created task:', record.id);
|
|
892
|
+
return record;
|
|
893
|
+
} catch (err) {
|
|
894
|
+
console.error('Error creating task:', err);
|
|
895
|
+
throw err;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Get all active tasks
|
|
900
|
+
async function getActiveTasks() {
|
|
901
|
+
try {
|
|
902
|
+
const records = await tasksTable.select({
|
|
903
|
+
filterByFormula: "AND({Status} != 'Done', {Status} != 'Cancelled')",
|
|
904
|
+
sort: [
|
|
905
|
+
{field: 'Priority', direction: 'desc'},
|
|
906
|
+
{field: 'Due Date', direction: 'asc'}
|
|
907
|
+
]
|
|
908
|
+
}).all();
|
|
909
|
+
|
|
910
|
+
return records.map(record => ({
|
|
911
|
+
id: record.id,
|
|
912
|
+
name: record.get('Name'),
|
|
913
|
+
status: record.get('Status'),
|
|
914
|
+
priority: record.get('Priority'),
|
|
915
|
+
assignee: record.get('Assignee'),
|
|
916
|
+
dueDate: record.get('Due Date')
|
|
917
|
+
}));
|
|
918
|
+
} catch (err) {
|
|
919
|
+
console.error('Error fetching tasks:', err);
|
|
920
|
+
throw err;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Update task status
|
|
925
|
+
async function updateTaskStatus(taskId, newStatus) {
|
|
926
|
+
try {
|
|
927
|
+
const record = await tasksTable.update(taskId, {
|
|
928
|
+
'Status': newStatus,
|
|
929
|
+
'Last Modified': new Date().toISOString()
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
console.log('Updated task:', record.id);
|
|
933
|
+
return record;
|
|
934
|
+
} catch (err) {
|
|
935
|
+
console.error('Error updating task:', err);
|
|
936
|
+
throw err;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Get overdue tasks
|
|
941
|
+
async function getOverdueTasks() {
|
|
942
|
+
try {
|
|
943
|
+
const today = new Date().toISOString().split('T')[0];
|
|
944
|
+
|
|
945
|
+
const records = await tasksTable.select({
|
|
946
|
+
filterByFormula: `AND(
|
|
947
|
+
{Status} != 'Done',
|
|
948
|
+
{Due Date} < '${today}'
|
|
949
|
+
)`,
|
|
950
|
+
sort: [{field: 'Due Date', direction: 'asc'}]
|
|
951
|
+
}).all();
|
|
952
|
+
|
|
953
|
+
return records;
|
|
954
|
+
} catch (err) {
|
|
955
|
+
console.error('Error fetching overdue tasks:', err);
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Bulk update multiple tasks
|
|
961
|
+
async function bulkUpdateStatus(taskIds, newStatus) {
|
|
962
|
+
try {
|
|
963
|
+
const updates = taskIds.map(id => ({
|
|
964
|
+
id: id,
|
|
965
|
+
fields: {
|
|
966
|
+
'Status': newStatus,
|
|
967
|
+
'Last Modified': new Date().toISOString()
|
|
968
|
+
}
|
|
969
|
+
}));
|
|
970
|
+
|
|
971
|
+
// Process in batches of 10
|
|
972
|
+
const results = [];
|
|
973
|
+
for (let i = 0; i < updates.length; i += 10) {
|
|
974
|
+
const batch = updates.slice(i, i + 10);
|
|
975
|
+
const updated = await tasksTable.update(batch);
|
|
976
|
+
results.push(...updated);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
console.log(`Updated ${results.length} tasks`);
|
|
980
|
+
return results;
|
|
981
|
+
} catch (err) {
|
|
982
|
+
console.error('Error bulk updating:', err);
|
|
983
|
+
throw err;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Delete completed tasks older than 30 days
|
|
988
|
+
async function deleteOldCompletedTasks() {
|
|
989
|
+
try {
|
|
990
|
+
const thirtyDaysAgo = new Date();
|
|
991
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
992
|
+
const cutoffDate = thirtyDaysAgo.toISOString().split('T')[0];
|
|
993
|
+
|
|
994
|
+
const records = await tasksTable.select({
|
|
995
|
+
filterByFormula: `AND(
|
|
996
|
+
{Status} = 'Done',
|
|
997
|
+
{Completed Date} < '${cutoffDate}'
|
|
998
|
+
)`
|
|
999
|
+
}).all();
|
|
1000
|
+
|
|
1001
|
+
const recordIds = records.map(record => record.id);
|
|
1002
|
+
|
|
1003
|
+
// Delete in batches of 10
|
|
1004
|
+
for (let i = 0; i < recordIds.length; i += 10) {
|
|
1005
|
+
const batch = recordIds.slice(i, i + 10);
|
|
1006
|
+
await tasksTable.destroy(batch);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
console.log(`Deleted ${recordIds.length} old completed tasks`);
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
console.error('Error deleting old tasks:', err);
|
|
1012
|
+
throw err;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Export module
|
|
1017
|
+
module.exports = {
|
|
1018
|
+
createTask,
|
|
1019
|
+
getActiveTasks,
|
|
1020
|
+
updateTaskStatus,
|
|
1021
|
+
getOverdueTasks,
|
|
1022
|
+
bulkUpdateStatus,
|
|
1023
|
+
deleteOldCompletedTasks
|
|
1024
|
+
};
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
### Example 2: Contact Management with Error Handling
|
|
1028
|
+
|
|
1029
|
+
```javascript
|
|
1030
|
+
const Airtable = require('airtable');
|
|
1031
|
+
|
|
1032
|
+
const airtable = new Airtable({
|
|
1033
|
+
apiKey: process.env.AIRTABLE_API_KEY
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
const base = airtable.base('appContactManager');
|
|
1037
|
+
const contactsTable = base('Contacts');
|
|
1038
|
+
|
|
1039
|
+
// Find contact by email
|
|
1040
|
+
async function findContactByEmail(email) {
|
|
1041
|
+
try {
|
|
1042
|
+
const records = await contactsTable.select({
|
|
1043
|
+
filterByFormula: `{Email} = "${email}"`,
|
|
1044
|
+
maxRecords: 1
|
|
1045
|
+
}).firstPage();
|
|
1046
|
+
|
|
1047
|
+
return records.length > 0 ? records[0] : null;
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
console.error(`Error finding contact with email ${email}:`, err);
|
|
1050
|
+
throw err;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Create or update contact (upsert pattern)
|
|
1055
|
+
async function upsertContact(contactData) {
|
|
1056
|
+
try {
|
|
1057
|
+
// First, try to find existing contact
|
|
1058
|
+
const existingContact = await findContactByEmail(contactData.email);
|
|
1059
|
+
|
|
1060
|
+
if (existingContact) {
|
|
1061
|
+
// Update existing contact
|
|
1062
|
+
const updated = await contactsTable.update(existingContact.id, {
|
|
1063
|
+
'First Name': contactData.firstName,
|
|
1064
|
+
'Last Name': contactData.lastName,
|
|
1065
|
+
'Phone': contactData.phone,
|
|
1066
|
+
'Company': contactData.company,
|
|
1067
|
+
'Last Updated': new Date().toISOString()
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
console.log('Updated existing contact:', updated.id);
|
|
1071
|
+
return {record: updated, action: 'updated'};
|
|
1072
|
+
} else {
|
|
1073
|
+
// Create new contact
|
|
1074
|
+
const created = await contactsTable.create({
|
|
1075
|
+
'First Name': contactData.firstName,
|
|
1076
|
+
'Last Name': contactData.lastName,
|
|
1077
|
+
'Email': contactData.email,
|
|
1078
|
+
'Phone': contactData.phone,
|
|
1079
|
+
'Company': contactData.company,
|
|
1080
|
+
'Created': new Date().toISOString()
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
console.log('Created new contact:', created.id);
|
|
1084
|
+
return {record: created, action: 'created'};
|
|
1085
|
+
}
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
console.error('Error upserting contact:', err);
|
|
1088
|
+
throw err;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Get all contacts from a company
|
|
1093
|
+
async function getContactsByCompany(companyName) {
|
|
1094
|
+
try {
|
|
1095
|
+
const records = await contactsTable.select({
|
|
1096
|
+
filterByFormula: `{Company} = "${companyName}"`,
|
|
1097
|
+
sort: [
|
|
1098
|
+
{field: 'Last Name', direction: 'asc'},
|
|
1099
|
+
{field: 'First Name', direction: 'asc'}
|
|
1100
|
+
]
|
|
1101
|
+
}).all();
|
|
1102
|
+
|
|
1103
|
+
return records.map(record => ({
|
|
1104
|
+
id: record.id,
|
|
1105
|
+
name: `${record.get('First Name')} ${record.get('Last Name')}`,
|
|
1106
|
+
email: record.get('Email'),
|
|
1107
|
+
phone: record.get('Phone')
|
|
1108
|
+
}));
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
console.error(`Error fetching contacts for ${companyName}:`, err);
|
|
1111
|
+
throw err;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Export contacts to array
|
|
1116
|
+
async function exportAllContacts() {
|
|
1117
|
+
try {
|
|
1118
|
+
const allContacts = [];
|
|
1119
|
+
|
|
1120
|
+
await contactsTable.select().eachPage(
|
|
1121
|
+
function page(records, fetchNextPage) {
|
|
1122
|
+
records.forEach(record => {
|
|
1123
|
+
allContacts.push({
|
|
1124
|
+
id: record.id,
|
|
1125
|
+
firstName: record.get('First Name'),
|
|
1126
|
+
lastName: record.get('Last Name'),
|
|
1127
|
+
email: record.get('Email'),
|
|
1128
|
+
phone: record.get('Phone'),
|
|
1129
|
+
company: record.get('Company'),
|
|
1130
|
+
created: record.get('Created')
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
fetchNextPage();
|
|
1135
|
+
},
|
|
1136
|
+
function done(err) {
|
|
1137
|
+
if (err) {
|
|
1138
|
+
console.error('Error during pagination:', err);
|
|
1139
|
+
throw err;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
return allContacts;
|
|
1145
|
+
} catch (err) {
|
|
1146
|
+
console.error('Error exporting contacts:', err);
|
|
1147
|
+
throw err;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
module.exports = {
|
|
1152
|
+
findContactByEmail,
|
|
1153
|
+
upsertContact,
|
|
1154
|
+
getContactsByCompany,
|
|
1155
|
+
exportAllContacts
|
|
1156
|
+
};
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
### Example 3: E-commerce Inventory Management
|
|
1160
|
+
|
|
1161
|
+
```javascript
|
|
1162
|
+
const Airtable = require('airtable');
|
|
1163
|
+
const base = Airtable.base('appInventory');
|
|
1164
|
+
|
|
1165
|
+
const productsTable = base('Products');
|
|
1166
|
+
const ordersTable = base('Orders');
|
|
1167
|
+
|
|
1168
|
+
// Check product availability
|
|
1169
|
+
async function checkInventory(productId) {
|
|
1170
|
+
try {
|
|
1171
|
+
const product = await productsTable.find(productId);
|
|
1172
|
+
|
|
1173
|
+
return {
|
|
1174
|
+
id: product.id,
|
|
1175
|
+
name: product.get('Name'),
|
|
1176
|
+
sku: product.get('SKU'),
|
|
1177
|
+
quantity: product.get('Quantity in Stock'),
|
|
1178
|
+
available: product.get('Quantity in Stock') > 0
|
|
1179
|
+
};
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
console.error(`Error checking inventory for ${productId}:`, err);
|
|
1182
|
+
throw err;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Update stock after purchase
|
|
1187
|
+
async function updateStockQuantity(productId, quantityChange) {
|
|
1188
|
+
try {
|
|
1189
|
+
const product = await productsTable.find(productId);
|
|
1190
|
+
const currentStock = product.get('Quantity in Stock');
|
|
1191
|
+
const newStock = currentStock + quantityChange;
|
|
1192
|
+
|
|
1193
|
+
if (newStock < 0) {
|
|
1194
|
+
throw new Error('Insufficient stock');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const updated = await productsTable.update(productId, {
|
|
1198
|
+
'Quantity in Stock': newStock,
|
|
1199
|
+
'Last Updated': new Date().toISOString()
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
console.log(`Updated stock for ${product.get('Name')}: ${currentStock} -> ${newStock}`);
|
|
1203
|
+
return updated;
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
console.error(`Error updating stock for ${productId}:`, err);
|
|
1206
|
+
throw err;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Get low stock products
|
|
1211
|
+
async function getLowStockProducts(threshold = 10) {
|
|
1212
|
+
try {
|
|
1213
|
+
const records = await productsTable.select({
|
|
1214
|
+
filterByFormula: `{Quantity in Stock} < ${threshold}`,
|
|
1215
|
+
sort: [{field: 'Quantity in Stock', direction: 'asc'}]
|
|
1216
|
+
}).all();
|
|
1217
|
+
|
|
1218
|
+
return records.map(record => ({
|
|
1219
|
+
id: record.id,
|
|
1220
|
+
name: record.get('Name'),
|
|
1221
|
+
sku: record.get('SKU'),
|
|
1222
|
+
quantity: record.get('Quantity in Stock'),
|
|
1223
|
+
reorderLevel: record.get('Reorder Level')
|
|
1224
|
+
}));
|
|
1225
|
+
} catch (err) {
|
|
1226
|
+
console.error('Error fetching low stock products:', err);
|
|
1227
|
+
throw err;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Create order with multiple line items
|
|
1232
|
+
async function createOrder(orderData) {
|
|
1233
|
+
try {
|
|
1234
|
+
// Create order record
|
|
1235
|
+
const order = await ordersTable.create({
|
|
1236
|
+
'Order Number': orderData.orderNumber,
|
|
1237
|
+
'Customer Name': orderData.customerName,
|
|
1238
|
+
'Customer Email': orderData.customerEmail,
|
|
1239
|
+
'Status': 'Pending',
|
|
1240
|
+
'Total Amount': orderData.totalAmount,
|
|
1241
|
+
'Order Date': new Date().toISOString()
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
// Update inventory for each product
|
|
1245
|
+
for (const item of orderData.items) {
|
|
1246
|
+
await updateStockQuantity(item.productId, -item.quantity);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
console.log('Created order:', order.id);
|
|
1250
|
+
return order;
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
console.error('Error creating order:', err);
|
|
1253
|
+
throw err;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Get orders by status
|
|
1258
|
+
async function getOrdersByStatus(status) {
|
|
1259
|
+
try {
|
|
1260
|
+
const records = await ordersTable.select({
|
|
1261
|
+
filterByFormula: `{Status} = "${status}"`,
|
|
1262
|
+
sort: [{field: 'Order Date', direction: 'desc'}]
|
|
1263
|
+
}).all();
|
|
1264
|
+
|
|
1265
|
+
return records.map(record => ({
|
|
1266
|
+
id: record.id,
|
|
1267
|
+
orderNumber: record.get('Order Number'),
|
|
1268
|
+
customer: record.get('Customer Name'),
|
|
1269
|
+
total: record.get('Total Amount'),
|
|
1270
|
+
date: record.get('Order Date')
|
|
1271
|
+
}));
|
|
1272
|
+
} catch (err) {
|
|
1273
|
+
console.error(`Error fetching ${status} orders:`, err);
|
|
1274
|
+
throw err;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
module.exports = {
|
|
1279
|
+
checkInventory,
|
|
1280
|
+
updateStockQuantity,
|
|
1281
|
+
getLowStockProducts,
|
|
1282
|
+
createOrder,
|
|
1283
|
+
getOrdersByStatus
|
|
1284
|
+
};
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
## Rate Limits and Error Handling
|
|
1288
|
+
|
|
1289
|
+
Airtable enforces a rate limit of **5 requests per second per base**.
|
|
1290
|
+
|
|
1291
|
+
### Handling Rate Limits
|
|
1292
|
+
|
|
1293
|
+
```javascript
|
|
1294
|
+
async function retryWithBackoff(fn, maxRetries = 3, initialDelay = 1000) {
|
|
1295
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
1296
|
+
try {
|
|
1297
|
+
return await fn();
|
|
1298
|
+
} catch (err) {
|
|
1299
|
+
if (err.statusCode === 429 && i < maxRetries - 1) {
|
|
1300
|
+
const delay = initialDelay * Math.pow(2, i);
|
|
1301
|
+
console.log(`Rate limited. Retrying in ${delay}ms...`);
|
|
1302
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1303
|
+
} else {
|
|
1304
|
+
throw err;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Usage
|
|
1311
|
+
const record = await retryWithBackoff(() =>
|
|
1312
|
+
table.create({'Name': 'New Record'})
|
|
1313
|
+
);
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
### Error Handling Patterns
|
|
1317
|
+
|
|
1318
|
+
```javascript
|
|
1319
|
+
const table = base('Tasks');
|
|
1320
|
+
|
|
1321
|
+
// Basic error handling
|
|
1322
|
+
try {
|
|
1323
|
+
const record = await table.find('recXXXXXXXXXXXXXX');
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
if (err.statusCode === 404) {
|
|
1326
|
+
console.error('Record not found');
|
|
1327
|
+
} else if (err.statusCode === 401) {
|
|
1328
|
+
console.error('Authentication failed');
|
|
1329
|
+
} else if (err.statusCode === 429) {
|
|
1330
|
+
console.error('Rate limit exceeded');
|
|
1331
|
+
} else {
|
|
1332
|
+
console.error('Unexpected error:', err);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// Comprehensive error handling
|
|
1337
|
+
async function safeCreate(recordData) {
|
|
1338
|
+
try {
|
|
1339
|
+
const record = await table.create(recordData);
|
|
1340
|
+
return {success: true, record};
|
|
1341
|
+
} catch (err) {
|
|
1342
|
+
console.error('Error creating record:', err.message);
|
|
1343
|
+
|
|
1344
|
+
return {
|
|
1345
|
+
success: false,
|
|
1346
|
+
error: {
|
|
1347
|
+
message: err.message,
|
|
1348
|
+
statusCode: err.statusCode,
|
|
1349
|
+
type: err.error
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
## TypeScript Support
|
|
1357
|
+
|
|
1358
|
+
```typescript
|
|
1359
|
+
import Airtable, { FieldSet, Records } from 'airtable';
|
|
1360
|
+
|
|
1361
|
+
interface Task extends FieldSet {
|
|
1362
|
+
'Name': string;
|
|
1363
|
+
'Status': 'To Do' | 'In Progress' | 'Done';
|
|
1364
|
+
'Priority': 'Low' | 'Medium' | 'High';
|
|
1365
|
+
'Due Date': string;
|
|
1366
|
+
'Assignee': string;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
Airtable.configure({
|
|
1370
|
+
apiKey: process.env.AIRTABLE_API_KEY!
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
const base = Airtable.base('appYourBaseId');
|
|
1374
|
+
const tasksTable = base<Task>('Tasks');
|
|
1375
|
+
|
|
1376
|
+
async function getHighPriorityTasks(): Promise<Records<Task>> {
|
|
1377
|
+
const records = await tasksTable.select({
|
|
1378
|
+
filterByFormula: "{Priority} = 'High'",
|
|
1379
|
+
sort: [{field: 'Due Date', direction: 'asc'}]
|
|
1380
|
+
}).all();
|
|
1381
|
+
|
|
1382
|
+
return records;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
async function createTask(taskData: Partial<Task>) {
|
|
1386
|
+
const record = await tasksTable.create(taskData);
|
|
1387
|
+
return record;
|
|
1388
|
+
}
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
## Common Formulas Reference
|
|
1392
|
+
|
|
1393
|
+
```javascript
|
|
1394
|
+
// Exact match
|
|
1395
|
+
"{Status} = 'Active'"
|
|
1396
|
+
|
|
1397
|
+
// Not equal
|
|
1398
|
+
"{Status} != 'Done'"
|
|
1399
|
+
|
|
1400
|
+
// Greater than / Less than
|
|
1401
|
+
"{Price} > 100"
|
|
1402
|
+
"{Stock} <= 10"
|
|
1403
|
+
|
|
1404
|
+
// String contains (case-sensitive)
|
|
1405
|
+
"FIND('urgent', {Notes}) > 0"
|
|
1406
|
+
|
|
1407
|
+
// String contains (case-insensitive)
|
|
1408
|
+
"SEARCH('urgent', LOWER({Notes})) > 0"
|
|
1409
|
+
|
|
1410
|
+
// Is empty
|
|
1411
|
+
"{Email} = ''"
|
|
1412
|
+
"OR({Email} = BLANK())"
|
|
1413
|
+
|
|
1414
|
+
// Is not empty
|
|
1415
|
+
"NOT({Email} = '')"
|
|
1416
|
+
"{Email} != ''"
|
|
1417
|
+
|
|
1418
|
+
// AND condition
|
|
1419
|
+
"AND({Status} = 'Active', {Priority} = 'High')"
|
|
1420
|
+
|
|
1421
|
+
// OR condition
|
|
1422
|
+
"OR({Status} = 'Urgent', {Priority} = 'High')"
|
|
1423
|
+
|
|
1424
|
+
// Date comparisons
|
|
1425
|
+
"{Created} > '2025-01-01'"
|
|
1426
|
+
"{Due Date} < TODAY()"
|
|
1427
|
+
"{Modified} >= DATEADD(TODAY(), -7, 'days')"
|
|
1428
|
+
|
|
1429
|
+
// Multiple conditions
|
|
1430
|
+
"AND({Status} = 'Active', OR({Priority} = 'High', {Due Date} < TODAY()))"
|
|
1431
|
+
|
|
1432
|
+
// Check if field is in a list
|
|
1433
|
+
"OR({Status} = 'Active', {Status} = 'In Progress', {Status} = 'Review')"
|
|
1434
|
+
|
|
1435
|
+
// Numeric range
|
|
1436
|
+
"AND({Price} >= 10, {Price} <= 100)"
|
|
1437
|
+
```
|