2023-02-15 06:00:28 +00:00
|
|
|
---
|
|
|
|
title: Airtable
|
2023-02-28 11:40:44 +00:00
|
|
|
pagination_prev: demos/local/index
|
|
|
|
pagination_next: demos/extensions/index
|
2023-02-15 06:00:28 +00:00
|
|
|
---
|
|
|
|
|
2023-04-27 09:12:19 +00:00
|
|
|
import current from '/version.js';
|
2023-05-07 13:58:36 +00:00
|
|
|
import CodeBlock from '@theme/CodeBlock';
|
2023-04-27 09:12:19 +00:00
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
[Airtable](https://airtable.com/) is a collaborative dataset hosting service.
|
|
|
|
|
|
|
|
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
|
|
|
|
data from spreadsheets.
|
|
|
|
|
|
|
|
This demo uses SheetJS to properly exchange data with spreadsheet files. We'll
|
|
|
|
explore how to use the `airtable` NodeJS library and SheetJS in two data flows:
|
|
|
|
|
|
|
|
- "Exporting data": Data in Airtable will be pulled into an array of objects and
|
|
|
|
exported to XLSB spreadsheets using SheetJS libraries.
|
|
|
|
|
|
|
|
- "Importing data": Data in an XLSX spreadsheet will be parsed using SheetJS
|
|
|
|
libraries and appended to a dataset in Airtable
|
|
|
|
|
|
|
|
|
|
|
|
## NodeJS Integration
|
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
### Installation
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be
|
|
|
|
required in NodeJS scripts that interact with Airtable.
|
|
|
|
|
|
|
|
The Airtable connector module is `airtable` and can be installed with `npm`:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-05-07 13:58:36 +00:00
|
|
|
<CodeBlock language="bash">{`\
|
2023-04-27 09:12:19 +00:00
|
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`}
|
2023-05-07 13:58:36 +00:00
|
|
|
</CodeBlock>
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
### Authentication
|
|
|
|
|
|
|
|
Airtable recommends Personal Access Tokens ("PAT") for interacting with the API.
|
|
|
|
|
|
|
|
:::note pass
|
|
|
|
|
|
|
|
The ["Personal Access Token"](#personal-access-token) section walks through the
|
|
|
|
process of creating a PAT.
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
The connector constructor accepts an options argument. The PAT should be passed
|
|
|
|
using the `apiKey` property:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
```js
|
2023-11-30 07:10:37 +00:00
|
|
|
const Airtable = require("airtable");
|
|
|
|
const apiKey = "PAT..."; // personal access token
|
|
|
|
const conn = new Airtable({apiKey, /* see docs for other options ... */});
|
|
|
|
```
|
|
|
|
|
|
|
|
The `base` method opens a specified workspace. The internal workspace name is
|
|
|
|
the first fragment in the Airtable URL, typically starts with "app":
|
|
|
|
|
|
|
|
```js
|
|
|
|
const base = conn.base("app...");
|
|
|
|
```
|
|
|
|
|
|
|
|
The `table` method of the workspace object opens a specified table:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const table = base.table("tablename...");
|
2023-02-15 06:00:28 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
### Exporting Data
|
|
|
|
|
|
|
|
When querying data, a result set will be a simple array of Record objects. The
|
2023-11-30 07:10:37 +00:00
|
|
|
`fields` property of each record object is a simple JS object. Mapping over the
|
|
|
|
result set and picking the `fields` field yields a standard array of objects:
|
|
|
|
|
|
|
|
```js
|
|
|
|
/** Create array of objects from Airtable table */
|
|
|
|
async function airtable_to_aoo(table) {
|
|
|
|
/* get all rows */
|
|
|
|
const result = await table.select().all();
|
|
|
|
|
|
|
|
/* pull raw objects from the result */
|
|
|
|
// highlight-next-line
|
|
|
|
const aoo = result.map(r => r.fields);
|
|
|
|
return aoo;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The SheetJS `json_to_sheet` utility function[^1] can generate a worksheet object
|
|
|
|
from the array of objects:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
```js
|
2023-11-30 07:10:37 +00:00
|
|
|
const XLSX = require("xlsx");
|
|
|
|
|
2023-02-15 06:00:28 +00:00
|
|
|
/** Create SheetJS worksheet from Airtable table */
|
|
|
|
async function airtable_to_worksheet(table) {
|
|
|
|
/* get all rows */
|
|
|
|
const result = await table.select().all();
|
|
|
|
|
|
|
|
/* pull raw objects from the result */
|
|
|
|
const aoo = result.map(r => r.fields);
|
|
|
|
|
|
|
|
/* create a worksheet */
|
2023-11-30 07:10:37 +00:00
|
|
|
// highlight-next-line
|
2023-02-15 06:00:28 +00:00
|
|
|
const worksheet = XLSX.utils.json_to_sheet(aoo);
|
|
|
|
return worksheet;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
:::caution pass
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
The results are not guaranteed to be sorted. The official API includes options
|
|
|
|
for sorting by fields.
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
The worksheet object must be added to a new workbook object using the `book_new`
|
|
|
|
and `book_append_sheet` helper functions[^2]:
|
|
|
|
|
|
|
|
```js
|
|
|
|
/** Create SheetJS workbook from Airtable table */
|
|
|
|
async function airtable_to_workbook(table) {
|
|
|
|
/* generate worksheet */
|
|
|
|
const ws = await airtable_to_worksheet(table);
|
|
|
|
/* create a new workbook */
|
|
|
|
const wb = XLSX.utils.book_new();
|
|
|
|
/* add worksheet to workbook */
|
|
|
|
XLSX.utils.book_append_sheet(wb, ws, "ExportedData");
|
|
|
|
return wb;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Local files can be created using the SheetJS `writeFile` method[^3]:
|
|
|
|
|
|
|
|
```js
|
|
|
|
(async() => {
|
|
|
|
/* generate SheetJS workbook */
|
|
|
|
const wb = await airtable_to_workbook(table);
|
|
|
|
/* write to XLSX */
|
|
|
|
XLSX.writeFile(wb, "SheetJSAirtableExport.xlsx");
|
|
|
|
})();
|
|
|
|
```
|
|
|
|
|
2023-02-15 06:00:28 +00:00
|
|
|
### Importing Data
|
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
The Airtable table `create` method expects an array of record objects. The
|
|
|
|
`fields` property of each object is expected to contain the raw record data.
|
|
|
|
|
|
|
|
Mapping over a standard array of objects can create Airtable-friendly data:
|
|
|
|
|
|
|
|
```js
|
|
|
|
/** Append records from an array of data objects to Airtable table */
|
|
|
|
async function airtable_load_aoo(table, aoo) {
|
|
|
|
/* reshape to be compatible with Airtable API */
|
|
|
|
// highlight-next-line
|
|
|
|
const airtable_rows = aoo.map(fields => ({ fields }));
|
|
|
|
|
|
|
|
/* upload data */
|
|
|
|
return await table.create(airtable_rows);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Starting from a SheetJS worksheet object[^4], the `sheet_to_json` method[^5] can
|
|
|
|
generate normal arrays of objects:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
```js
|
2023-11-30 07:10:37 +00:00
|
|
|
const XLSX = require("xlsx");
|
|
|
|
|
2023-02-15 06:00:28 +00:00
|
|
|
/** Append records from a SheetJS worksheet to Airtable table */
|
|
|
|
async function airtable_load_worksheet(table, worksheet) {
|
2023-11-30 07:10:37 +00:00
|
|
|
/* generate normal array of objects */
|
|
|
|
// highlight-next-line
|
2023-02-15 06:00:28 +00:00
|
|
|
const aoo = XLSX.utils.sheet_to_json(worksheet);
|
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
/* upload data */
|
|
|
|
return await airtable_load_aoo(table, aoo);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
A SheetJS worksheet object can be extracted from a workbook object[^6]:
|
|
|
|
|
|
|
|
```js
|
|
|
|
/** Append records from the first worksheet of a workbook to Airtable table */
|
|
|
|
async function airtable_load_workbook(table, workbook) {
|
|
|
|
/* pull first worksheet from workbook object */
|
2023-02-15 06:00:28 +00:00
|
|
|
// highlight-next-line
|
2023-11-30 07:10:37 +00:00
|
|
|
const first_sheet_name = workbook.SheetNames[0];
|
|
|
|
const ws = workbook.Sheets[first_sheet_name];
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
/* upload data */
|
2023-11-30 07:10:37 +00:00
|
|
|
return await airtable_load_worksheet(table, ws);
|
2023-02-15 06:00:28 +00:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
Local files can be read using the SheetJS `readFile` method[^7]:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const wb = XLSX.readFile("SheetJSAirtableTest.xlsb");
|
|
|
|
(async() => {
|
|
|
|
await airtable_load_workbook(table, wb);
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
2023-02-15 06:00:28 +00:00
|
|
|
## Complete Example
|
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
:::note Tested Deployments
|
2023-02-26 11:38:03 +00:00
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
This demo was last tested on 2023 September 03. At the time, free accounts
|
|
|
|
included limited API access.
|
2023-02-26 11:38:03 +00:00
|
|
|
|
|
|
|
:::
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
0) Create a free Airtable account and verify the email address.
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
### Personal Access Token
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
:::note pass
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
In the past, Airtable offered API keys. They are slated to deprecate API keys
|
|
|
|
in January 2024. They recommend "Personal Access Tokens" for operations.
|
|
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
API actions will require a PAT, which must be created through the developer hub:
|
|
|
|
|
|
|
|
1) Click on account icon (topright area of the page) and select "Developer Hub".
|
|
|
|
|
|
|
|
2) Click "Create Token".
|
|
|
|
|
|
|
|
3) In the form, make the following selections:
|
|
|
|
|
|
|
|
- Name: enter any name (for example, "SheetJS Test")
|
|
|
|
- Scopes: `data.records:read` and `data.records:write` (adding 2 scopes)
|
|
|
|
- Access: "All current and future bases in all current and future workspaces"
|
|
|
|
|
|
|
|
The form will look like the screenshot below:
|
|
|
|
|
|
|
|
![Airtable PAT Form](pathname:///airtable/pat.png)
|
|
|
|
|
|
|
|
4) Click "Create Token" and you will see a popup. Copy the token and save it.
|
|
|
|
|
|
|
|
### Workspace
|
|
|
|
|
|
|
|
For the purposes of this demo, a sample workspace should be created:
|
|
|
|
|
2024-04-08 04:47:04 +00:00
|
|
|
5) Download https://sheetjs.com/pres.xlsx
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
6) Create a project in Airtable using "Quickly upload". Select "Microsoft Excel"
|
|
|
|
and select the downloaded file from step 1. Click "Upload", then "Import".
|
|
|
|
|
|
|
|
7) A workspace will be created. The name will be found in the URL. For example:
|
|
|
|
|
|
|
|
```
|
|
|
|
https://airtable.com/appblahblah/tblblahblah/blahblah
|
|
|
|
--------------------/^^^^^^^^^^^/ workspace name
|
|
|
|
```
|
|
|
|
|
|
|
|
the first part after the `.com` will be the workspace name.
|
|
|
|
|
|
|
|
### Exporting Data
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
8) Save the following to `SheetJSAirtableRead.js`:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
```js title="SheetJSAirtableRead.js"
|
2023-02-15 06:00:28 +00:00
|
|
|
const Airtable = require("airtable"), XLSX = require("xlsx");
|
|
|
|
// highlight-start
|
|
|
|
/* replace the value with the personal access token */
|
|
|
|
const apiKey = "pat...";
|
|
|
|
/* replace the value with the workspace name */
|
|
|
|
const base = "app...";
|
|
|
|
// highlight-end
|
|
|
|
(async() => {
|
|
|
|
const conn = new Airtable({ apiKey });
|
|
|
|
const table = conn.base(base).table("Sheet1");
|
|
|
|
const result = await table.select().all();
|
|
|
|
const aoo = result.map(r => r.fields);
|
|
|
|
const ws = XLSX.utils.json_to_sheet(aoo);
|
|
|
|
const wb = XLSX.utils.book_new();
|
|
|
|
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
|
|
|
|
XLSX.writeFile(wb, "SheetJSAirtable.xlsb");
|
|
|
|
})();
|
|
|
|
```
|
|
|
|
|
|
|
|
9) Replace the values in the highlighted lines with the PAT and workspace name.
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
10) Install dependencies:
|
|
|
|
|
|
|
|
<CodeBlock language="bash">{`\
|
|
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`}
|
|
|
|
</CodeBlock>
|
|
|
|
|
|
|
|
11) Run the script:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
node SheetJSAirtableRead.js
|
|
|
|
```
|
|
|
|
|
|
|
|
The script should write `SheetJSAirtable.xlsb`. The file can be opened in Excel.
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
### Importing Data
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
12) Create a file `SheetJSAirpend.xlsx` with some new records in sheet `Sheet1`:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
|
|
|
![Records to append](pathname:///airtable/airpend.png)
|
|
|
|
|
|
|
|
`npx xlsx-cli SheetJSAirpend.xlsx` should print the following data:
|
|
|
|
|
|
|
|
```csv
|
|
|
|
Sheet1
|
|
|
|
Name,Index
|
|
|
|
Someone Else,47
|
|
|
|
```
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
13) Save the following to `SheetJSAirtableWrite.js`:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
```js title="SheetJSAirtableWrite.js"
|
2023-02-15 06:00:28 +00:00
|
|
|
const Airtable = require("airtable"), XLSX = require("xlsx");
|
|
|
|
// highlight-start
|
|
|
|
/* replace the value with the personal access token */
|
|
|
|
const apiKey = "pat...";
|
|
|
|
/* replace the value with the workspace name */
|
|
|
|
const base = "app...";
|
|
|
|
// highlight-end
|
|
|
|
(async() => {
|
|
|
|
const conn = new Airtable({ apiKey });
|
|
|
|
const table = conn.base(base).table("Sheet1");
|
|
|
|
const wb = XLSX.readFile("SheetJSAirpend.xlsx");
|
|
|
|
const ws = wb.Sheets["Sheet1"];
|
|
|
|
const aoo = XLSX.utils.sheet_to_json(ws);
|
|
|
|
await table.create(aoo.map(fields => ({ fields })));
|
|
|
|
})();
|
|
|
|
```
|
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
14) Replace the values in the highlighted lines with the PAT and workspace name.
|
|
|
|
|
|
|
|
15) Install dependencies:
|
|
|
|
|
|
|
|
<CodeBlock language="bash">{`\
|
|
|
|
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`}
|
|
|
|
</CodeBlock>
|
|
|
|
|
|
|
|
16) Run the script:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
node SheetJSAirtableWrite.js
|
|
|
|
```
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-09-05 18:04:23 +00:00
|
|
|
Open Airtable and verify the new row was added:
|
2023-02-15 06:00:28 +00:00
|
|
|
|
2023-11-30 07:10:37 +00:00
|
|
|
![Final Result in Airtable](pathname:///airtable/post.png)
|
|
|
|
|
|
|
|
[^1]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
|
|
|
|
[^2]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
|
|
|
|
[^3]: See [`writeFile` in "Writing Files"](/docs/api/write-options)
|
|
|
|
[^4]: See ["Sheet Objects"](/docs/csf/sheet) for more details/
|
|
|
|
[^5]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
|
|
|
|
[^6]: See ["Workbook Object"](/docs/csf/book)
|
|
|
|
[^7]: See [`readFile` in "Reading Files"](/docs/api/parse-options)
|