--- title: Flying Sheets with Airtable sidebar_label: Airtable pagination_prev: demos/local/index pagination_next: demos/extensions/index --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; [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 ### Installation 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`: {`\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`} ### 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: ```js 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..."); ``` ### Exporting Data When querying data, a result set will be a simple array of Record objects. The `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: ```js const XLSX = require("xlsx"); /** 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 */ // highlight-next-line const worksheet = XLSX.utils.json_to_sheet(aoo); return worksheet; } ``` :::caution pass The results are not guaranteed to be sorted. The official API includes options for sorting by fields. ::: 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"); })(); ``` ### Importing Data 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: ```js const XLSX = require("xlsx"); /** Append records from a SheetJS worksheet to Airtable table */ async function airtable_load_worksheet(table, worksheet) { /* generate normal array of objects */ // highlight-next-line const aoo = XLSX.utils.sheet_to_json(worksheet); /* 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 */ // highlight-next-line const first_sheet_name = workbook.SheetNames[0]; const ws = workbook.Sheets[first_sheet_name]; /* upload data */ return await airtable_load_worksheet(table, ws); } ``` 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); })(); ``` ## Complete Example :::note Tested Deployments This demo was last tested on 2025-04-21. In the most recent test, free accounts included limited API access. ::: 0) Create a free Airtable account and verify the email address. ### Personal Access Token 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 "Builder Hub". :::caution pass The email address associated with the account must be verified before attempting to create a token. ::: 2) Click "Personal access tokens" in the sidebar, then 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 following screenshot: ![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: 5) Download https://docs.sheetjs.com/pres.xlsx 6) Click the left arrow in the top-left corner to return to the home page. 7) In the left sidebar, scroll down and click the "Import" link. In the modal, select "Microsoft Excel". In the upload modal, click "browse files" and select `pres.xlsx` from Step 5. Click the blue "Upload 1 file" button. In the new modal, click "Import". When this demo was last tested, Airtable created 5 records with "Name" and "Index" column headers. 8) 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. Copy the name. ### New Project 9) Create a new project: ```bash mkdir -p sheetjs-airtable cd sheetjs-airtable npm init -y ``` 10) Install dependencies: {`\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`} ### Exporting Data 11) Save the following to `SheetJSAirtableRead.js`: ```js title="SheetJSAirtableRead.js" 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"); })(); ``` 12) Replace the values in the highlighted lines with the PAT and workspace name. 13) Run the script: ```bash node SheetJSAirtableRead.js ``` The script will export the data from Airtable to `SheetJSAirtable.xlsb`. The new spreadsheet can be opened in Excel. ### Importing Data 14) Download [`SheetJSAirpend.xlsx`](pathname:///airtable/SheetJSAirpend.xlsx) to the project folder: ```bash curl -LO https://docs.sheetjs.com/airtable/SheetJSAirpend.xlsx ``` 15) Save the following to `SheetJSAirtableWrite.js`: ```js title="SheetJSAirtableWrite.js" 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 }))); })(); ``` 16) Replace the values in the highlighted lines with the PAT and workspace name. 17) Run the script: ```bash node SheetJSAirtableWrite.js ``` Open Airtable and verify the new row was added: ![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)