Skip to content

JsWorkflows provides a platform-managed Airtable OAuth app. You do not need to create an Airtable integration or provide credentials.

Use this connector when your team already uses Airtable as an operational table, lightweight database, planning sheet, or review queue. A workflow can send Shopify data into Airtable, read Airtable records to drive Shopify updates, or keep Airtable in sync with store activity.

Good fits include:

  • Sending high-value orders, B2B requests, returns, or exception cases into an Airtable review table
  • Keeping a merchandising or product cleanup table updated from Shopify products and variants
  • Reading supplier, catalog, or campaign data from Airtable and applying it to Shopify records
  • Logging workflow outputs into Airtable for a team that does not work in Shopify every day
  • Creating operational queues for inventory checks, customer follow-up, fraud review, or custom fulfillment

Airtable is best when the data needs to be reviewed, filtered, edited, or reported by a team. If you only need a notification, Slack or email is usually simpler.

When connecting, select the access your workflow needs:

ResourceOperationScopes granted
RecordsRead recordsdata.records:read, schema.bases:read
RecordsRead/write recordsdata.records:read, data.records:write, schema.bases:read
Base SchemaRead base schemaschema.bases:read
Base SchemaRead/write base schemaschema.bases:read, schema.bases:write
Workspaces & BasesList workspaces & basesworkspacesAndBases:read
Record CommentsRead commentsdata.recordComments:read
Record CommentsRead/write commentsdata.recordComments:read, data.recordComments:write
WebhooksManage webhookswebhook:manage
User ProfileRead user emailuser.email:read

Select the minimal set your workflow requires. You can always delete the connection and reconnect with broader scopes later.

  1. Go to Settings → OAuth2 Tokens, click Connect to Service, and choose Airtable.
  2. Select the resources and operations your workflow needs.
  3. Give the connection a handle (e.g., my-airtable) and click Generate and Authorize.
  4. A popup opens Airtable’s authorization screen. Choose which bases to grant access to and click Grant access.
  5. The connection appears in your list.

Use lowercase letters, numbers, and hyphens only for the handle.

Airtable issues refresh tokens automatically. JsWorkflows refreshes the access token before it expires — you do not need to reconnect unless you revoke access from Airtable.

Airtable’s Web API is a REST API. Record requests normally use:

https://api.airtable.com/v0/{baseId}/{tableIdOrName}

For production workflows, prefer table IDs such as tblXXXXXXXXXXXXXX instead of table names. Table names work, but IDs are safer if someone renames the table later.

Most record work uses:

  • GET /v0/{baseId}/{tableIdOrName} to list records
  • POST /v0/{baseId}/{tableIdOrName} to create records
  • PATCH /v0/{baseId}/{tableIdOrName}/{recordId} to update one record
  • PATCH /v0/{baseId}/{tableIdOrName} to update multiple records

Create a small helper so every step handles tokens, query parameters, HTTP errors, and Airtable error bodies consistently.

async function airtableRequest(api, path, options = {}) {
const { method = 'GET', query = {}, body } = options;
const { token, error } = await api.getOAuthToken('my-airtable');
if (error || !token) throw new Error(error || 'Missing Airtable token');
const url = new URL(`https://api.airtable.com/v0${path}`);
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === null || value === '') return;
if (Array.isArray(value)) {
value.forEach(item => url.searchParams.append(key, item));
} else {
url.searchParams.set(key, value);
}
});
const res = await fetch(url.toString(), {
method,
headers: {
Authorization: `Bearer ${token}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let json = {};
try {
json = text ? JSON.parse(text) : {};
} catch (_error) {
json = { raw: text };
}
if (!res.ok) {
throw new Error(`Airtable HTTP ${res.status}: ${JSON.stringify(json)}`);
}
return json;
}
export class Workflow {
async start(_data, _headers, api) {
const { token, error } = await api.getOAuthToken('my-airtable');
if (error || !token) throw new Error(error || 'Missing Airtable token');
const baseId = 'appXXXXXXXXXXXXXX';
const tableId = 'tblXXXXXXXXXXXXXX';
const res = await fetch(
`https://api.airtable.com/v0/${baseId}/${tableId}?maxRecords=10`,
{ headers: { Authorization: `Bearer ${token}` } }
);
if (!res.ok) throw new Error(`Airtable ${res.status}: ${await res.text()}`);
const { records } = await res.json();
console.log(`Fetched ${records.length} records`);
}
}

Replace appXXXXXXXXXXXXXX with your base ID and tblXXXXXXXXXXXXXX with your table ID. Both are visible in the Airtable URL when you open the table: https://airtable.com/appXXX/tblXXX/....

Airtable returns records in pages. If there are more records, the response includes an offset; pass that offset into the next request until no offset is returned.

async function listAllAirtableRecords(api, baseId, tableId) {
const records = [];
let offset;
do {
const page = await airtableRequest(api, `/${baseId}/${tableId}`, {
query: {
pageSize: 100,
offset,
},
});
records.push(...(page.records || []));
offset = page.offset;
} while (offset);
return records;
}

Use pagination when a workflow needs to scan a table, match records by SKU, read supplier data, or sync more than the first page of rows.

const { token, error } = await api.getOAuthToken('my-airtable');
if (error || !token) throw new Error(error || 'Missing Airtable token');
const baseId = 'appXXXXXXXXXXXXXX';
const tableId = 'tblXXXXXXXXXXXXXX';
const res = await fetch(`https://api.airtable.com/v0/${baseId}/${tableId}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
fields: {
Name: 'New order',
Status: 'Pending',
Amount: 99.95,
},
}),
});
if (!res.ok) throw new Error(`Airtable ${res.status}: ${await res.text()}`);
const record = await res.json();
console.log(`Created record ${record.id}`);

Field names must match the exact column names in your Airtable table. The field types (text, number, select, etc.) must also match the table definition.

Example: create a Shopify order review record

Section titled “Example: create a Shopify order review record”

This example creates one Airtable record when a workflow receives an order payload. It is useful for review queues, custom fulfillment, B2B order handling, or exceptions that a team needs to inspect outside Shopify.

const AIRTABLE_BASE_ID = 'appXXXXXXXXXXXXXX';
const AIRTABLE_TABLE_ID = 'tblXXXXXXXXXXXXXX';
export class Workflow {
async start(order, _headers, api) {
const orderName = order.name || `Order ${order.id}`;
const customerEmail = order.email || order.customer?.email || '';
const total = Number(order.current_total_price || order.total_price || 0);
const record = await airtableRequest(api, `/${AIRTABLE_BASE_ID}/${AIRTABLE_TABLE_ID}`, {
method: 'POST',
body: {
fields: {
'Order': orderName,
'Customer email': customerEmail,
'Total': total,
'Status': 'Needs review',
'Notes': `Created by JsWorkflows for ${orderName}`,
},
},
});
console.log({ status: 'airtable-record-created', recordId: record.id });
}
}

Replace the field names with the actual Airtable field names in your table.

Requires the Workspaces & Bases → List workspaces & bases scope.

const { token, error } = await api.getOAuthToken('my-airtable');
if (error || !token) throw new Error(error || 'Missing Airtable token');
const res = await fetch('https://api.airtable.com/v0/meta/bases', {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`Airtable ${res.status}: ${await res.text()}`);
const { bases } = await res.json();
bases.forEach(b => console.log(b.id, b.name));

The easiest way to find IDs is from the URL when viewing a table in Airtable:

https://airtable.com/appABCDEFGHIJKLMN/tblOPQRSTUVWXYZ12/...
↑ base ID ↑ table ID

You can also retrieve them programmatically using the metadata API:

// List tables in a base
const { token } = await api.getOAuthToken('my-airtable');
const baseId = 'appXXXXXXXXXXXXXX';
const res = await fetch(`https://api.airtable.com/v0/meta/bases/${baseId}/tables`, {
headers: { Authorization: `Bearer ${token}` },
});
const { tables } = await res.json();
tables.forEach(t => console.log(t.id, t.name));

Requires the Base Schema → Read base schema scope.

Airtable field values must match the field type in the table:

  • Text fields accept strings.
  • Number, currency, and percent fields should receive numbers, not formatted strings.
  • Single select fields usually need an existing option name unless your Airtable settings/API usage allow creating options.
  • Multiple select fields use an array of option names.
  • Linked record fields use an array of Airtable record IDs, such as ['recXXXXXXXXXXXXXX'].
  • Attachment fields use an array of objects with a public url, and optionally filename.
  • Empty fields may be omitted from Airtable API responses, so do not assume every field key exists.
  • Airtable returns up to 100 records per page. Use the offset value to fetch additional pages.
  • Airtable has per-base rate limits. For large syncs, process records in batches and schedule follow-up steps instead of making many requests in one step.
  • Store base IDs, table IDs, field names, and view names as setup/config values in production workflows when merchants need to change them.
  • Use Airtable for shared operational data, review queues, and planning tables. Avoid writing every routine Shopify event to Airtable unless the team actually needs to work with that data there.