diff --git a/docz/docs/03-demos/11-static/01-lume.md b/docz/docs/03-demos/11-static/01-lume.md
index 73e3297..a11e255 100644
--- a/docz/docs/03-demos/11-static/01-lume.md
+++ b/docz/docs/03-demos/11-static/01-lume.md
@@ -1,7 +1,7 @@
 ---
 title: Lume
 pagination_prev: demos/extensions/index
-pagination_next: demos/gsheet
+pagination_next: demos/cloudata/index
 sidebar_custom_props:
   type: native
 ---
diff --git a/docz/docs/03-demos/11-static/02-gatsbyjs.md b/docz/docs/03-demos/11-static/02-gatsbyjs.md
index 3ae9f9d..e9eeeae 100644
--- a/docz/docs/03-demos/11-static/02-gatsbyjs.md
+++ b/docz/docs/03-demos/11-static/02-gatsbyjs.md
@@ -1,7 +1,7 @@
 ---
 title: GatsbyJS
 pagination_prev: demos/extensions/index
-pagination_next: demos/gsheet
+pagination_next: demos/cloudata/index
 sidebar_custom_props:
   type: native
 ---
diff --git a/docz/docs/03-demos/11-static/05-vitejs.md b/docz/docs/03-demos/11-static/05-vitejs.md
index 9055fec..63c95b2 100644
--- a/docz/docs/03-demos/11-static/05-vitejs.md
+++ b/docz/docs/03-demos/11-static/05-vitejs.md
@@ -1,7 +1,7 @@
 ---
 title: ViteJS
 pagination_prev: demos/extensions/index
-pagination_next: demos/gsheet
+pagination_next: demos/cloudata/index
 sidebar_custom_props:
   type: bundler
 ---
diff --git a/docz/docs/03-demos/11-static/08-nextjs.md b/docz/docs/03-demos/11-static/08-nextjs.md
index fb742d2..aeffb2a 100644
--- a/docz/docs/03-demos/11-static/08-nextjs.md
+++ b/docz/docs/03-demos/11-static/08-nextjs.md
@@ -1,7 +1,7 @@
 ---
 title: NextJS
 pagination_prev: demos/extensions/index
-pagination_next: demos/gsheet
+pagination_next: demos/cloudata/index
 ---
 
 :::note
diff --git a/docz/docs/03-demos/11-static/09-nuxtjs.md b/docz/docs/03-demos/11-static/09-nuxtjs.md
index ff01e70..220dc14 100644
--- a/docz/docs/03-demos/11-static/09-nuxtjs.md
+++ b/docz/docs/03-demos/11-static/09-nuxtjs.md
@@ -1,7 +1,7 @@
 ---
 title: NuxtJS
 pagination_prev: demos/extensions/index
-pagination_next: demos/gsheet
+pagination_next: demos/cloudata/index
 ---
 
 `@nuxt/content` is a file-based CMS for Nuxt, enabling static-site generation
diff --git a/docz/docs/03-demos/11-static/index.md b/docz/docs/03-demos/11-static/index.md
index 61e79b4..f6b499b 100644
--- a/docz/docs/03-demos/11-static/index.md
+++ b/docz/docs/03-demos/11-static/index.md
@@ -1,7 +1,7 @@
 ---
 title: Content and Static Sites
 pagination_prev: demos/extensions/index
-pagination_next: demos/gsheet
+pagination_next: demos/cloudata/index
 ---
 
 import DocCardList from '@theme/DocCardList';
diff --git a/docz/docs/03-demos/25-gsheet.md b/docz/docs/03-demos/19-cloudata/01-gsheet.md
similarity index 96%
rename from docz/docs/03-demos/25-gsheet.md
rename to docz/docs/03-demos/19-cloudata/01-gsheet.md
index 4da556b..ac5c36f 100644
--- a/docz/docs/03-demos/25-gsheet.md
+++ b/docz/docs/03-demos/19-cloudata/01-gsheet.md
@@ -1,6 +1,7 @@
 ---
 title: Google Sheets
 pagination_prev: demos/static/index
+pagination_next: demos/cli
 ---
 
 import Tabs from '@theme/Tabs';
diff --git a/docz/docs/03-demos/19-cloudata/08-airtable.md b/docz/docs/03-demos/19-cloudata/08-airtable.md
new file mode 100644
index 0000000..9e758a2
--- /dev/null
+++ b/docz/docs/03-demos/19-cloudata/08-airtable.md
@@ -0,0 +1,193 @@
+---
+title: Airtable
+pagination_prev: demos/static/index
+pagination_next: demos/cli
+---
+
+At the time of writing (2023 February 15), Airtable recommends Personal Access
+Tokens for interacting with the official API.
+
+## NodeJS Integration
+
+The main module is `airtable` and can be installed with `npm`:
+
+```bash
+npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz airtable
+```
+
+To obtain a reference to a table, code needs a [PAT](#personal-access-token),
+the name of the workspace (typically starting with `app`), and the name of the
+desired table (the Excel import typically supports the worksheet name):
+
+```js
+const Airtable = require("airtable"), XLSX = require("xlsx");
+/* query all records in a table */
+const conn = new Airtable({apiKey: "PAT...", /* and other options ... */});
+const table = conn.base("app...").table("tablename...");
+```
+
+### Exporting Data
+
+When querying data, a result set will be a simple array of Record objects. The
+`fields` property is a simple JS object compatible with `json_to_sheet`:
+
+```js
+/** 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 */
+  // highlight-next-line
+  const aoo = result.map(r => r.fields);
+
+  /* create a worksheet */
+  const worksheet = XLSX.utils.json_to_sheet(aoo);
+  return worksheet;
+}
+```
+
+:::caution
+
+The results are not guaranteed to be sorted.  The official API includes options
+for sorting by fields.
+
+:::
+
+### Importing Data
+
+When inserting records, each object should be wrapped in a parent object with a
+single `fields` property:
+
+```js
+/** Append records from a SheetJS worksheet to Airtable table */
+async function airtable_load_worksheet(table, worksheet) {
+  /* suppose the field names */
+  const aoo = XLSX.utils.sheet_to_json(worksheet);
+
+  /* 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);
+}
+```
+
+## Complete Example
+
+0) Create a free Airtable account.
+
+### Personal Access Token
+
+:::note
+
+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:
+
+5) Download <https://sheetjs.com/pres.xlsx>
+
+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
+
+8) Save the following to `read.js`:
+
+```js title="read.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");
+})();
+```
+
+9) Replace the values in the highlighted lines with the PAT and workspace name.
+
+10) Run `node read.js`.  The script should write `SheetJSAirtable.xlsb`. The file
+can be opened in Excel.
+
+### Importing Data
+
+11) Create a file `SheetJSAirpend.xlsx` with some new records in sheet `Sheet1`:
+
+![Records to append](pathname:///airtable/airpend.png)
+
+`npx xlsx-cli SheetJSAirpend.xlsx` should print the following data:
+
+```csv
+Sheet1
+Name,Index
+Someone Else,47
+```
+
+12) Save the following to `write.js`:
+
+```js title="write.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 })));
+})();
+```
+
+13) Replace the values in the highlighted lines with the PAT and workspace name.
+
+14) Run `node write.js`.  Open Airtable and verify the new row:
+
+![Final Result in Airtable](pathname:///airtable/post.png)
\ No newline at end of file
diff --git a/docz/docs/03-demos/19-cloudata/_category_.json b/docz/docs/03-demos/19-cloudata/_category_.json
new file mode 100644
index 0000000..015a336
--- /dev/null
+++ b/docz/docs/03-demos/19-cloudata/_category_.json
@@ -0,0 +1,5 @@
+{
+  "label": "Cloud Data Platforms",
+  "position": 19,
+  "collapsed": false
+}
\ No newline at end of file
diff --git a/docz/docs/03-demos/19-cloudata/index.md b/docz/docs/03-demos/19-cloudata/index.md
new file mode 100644
index 0000000..4b0cca3
--- /dev/null
+++ b/docz/docs/03-demos/19-cloudata/index.md
@@ -0,0 +1,20 @@
+---
+title: Cloud Data Platforms
+pagination_prev: demos/static/index
+pagination_next: demos/cli
+---
+
+import DocCardList from '@theme/DocCardList';
+import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
+
+Cloud Data Platforms are popular storage media for structured data, typically
+offering APIs for programmatic data ingress and egress. Demos:
+
+<ul>{useCurrentSidebarCategory().items.map(item => {
+  const listyle = (item.customProps?.icon) ? {
+    listStyleImage: `url("${item.customProps.icon}")`
+  } : {};
+  return (<li style={listyle} {...(item.customProps?.class ? {className: item.customProps.class}: {})}>
+    <a href={item.href}>{item.label}</a>{item.customProps?.summary && (" - " + item.customProps.summary)}
+  </li>);
+})}</ul>
diff --git a/docz/docs/03-demos/27-cli.md b/docz/docs/03-demos/27-cli.md
index 48daeed..abc6516 100644
--- a/docz/docs/03-demos/27-cli.md
+++ b/docz/docs/03-demos/27-cli.md
@@ -1,5 +1,6 @@
 ---
 title: Command-Line Tools
+pagination_prev: demos/cloudata/index
 pagination_next: demos/engines/index
 ---
 
diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md
index 97628f3..857ca73 100644
--- a/docz/docs/03-demos/index.md
+++ b/docz/docs/03-demos/index.md
@@ -83,32 +83,46 @@ run in the web browser, demos will include interactive examples.
 - [`Dropbox`](/docs/demos/hosting/dropbox)
 - [`GitHub`](/docs/demos/hosting/github)
 
+### Cloud Data Services
+
+- [`Google Sheets`](/docs/demos/cloudata/gsheet)
+- [`Airtable`](/docs/demos/cloudata/airtable)
+
 ### Platforms and Integrations
 
 - [`Command-Line Tools`](/docs/demos/cli)
 - [`NodeJS Server-Side Processing`](/docs/demos/server#nodejs)
 - [`Deno Server-Side Processing`](/docs/demos/server#deno)
-- [`Google Sheets API`](/docs/demos/gsheet)
 - [`Headless Automation`](/docs/demos/headless)
-- [`Other JavaScript Engines`](/docs/demos/engines)
 - [`Databases and Structured Data Stores`](/docs/demos/database)
 - [`NoSQL and Unstructured Data Stores`](/docs/demos/nosql)
 - [`Legacy Internet Explorer`](/docs/demos/frontend/legacy#internet-explorer)
 
 ### Bundlers and Tooling
 
-- [`browserify`](/docs/demos/bundler#browserify)
-- [`bun`](/docs/demos/bundler#bun)
-- [`esbuild`](/docs/demos/bundler#esbuild)
-- [`parcel`](/docs/demos/bundler#parcel)
-- [`requirejs`](/docs/demos/bundler#requirejs)
-- [`rollup`](/docs/demos/bundler#rollup)
-- [`snowpack`](/docs/demos/bundler#snowpack)
-- [`swc`](/docs/demos/bundler#swc)
-- [`systemjs`](/docs/demos/bundler#systemjs)
-- [`vite`](/docs/demos/bundler#vite)
-- [`webpack`](/docs/demos/bundler#webpack)
-- [`wmr`](/docs/demos/bundler#wmr)
+- [`browserify`](/docs/demos/frontend/bundler#browserify)
+- [`bun`](/docs/demos/frontend/bundler#bun)
+- [`esbuild`](/docs/demos/frontend/bundler#esbuild)
+- [`parcel`](/docs/demos/frontend/bundler#parcel)
+- [`requirejs`](/docs/demos/frontend/bundler#requirejs)
+- [`rollup`](/docs/demos/frontend/bundler#rollup)
+- [`snowpack`](/docs/demos/frontend/bundler#snowpack)
+- [`swc`](/docs/demos/frontend/bundler#swc)
+- [`systemjs`](/docs/demos/frontend/bundler#systemjs)
+- [`vite`](/docs/demos/frontend/bundler#vite)
+- [`webpack`](/docs/demos/frontend/bundler#webpack)
+- [`wmr`](/docs/demos/frontend/bundler#wmr)
+
+### Other Programming Languages
+
+- [`JavaScript Engines`](/docs/demos/engines)
+- [`Duktape (C)`](/docs/demos/engines/duktape)
+- [`JavaScriptCore (Swift)`](/docs/demos/engines/jsc)
+- [`Rhino (Java)`](/docs/demos/engines/duktape)
+- [`Goja (Go)`](/docs/demos/engines/duktape)
+- [`ExecJS (Ruby)`](/docs/demos/engines/duktape)
+- [`JavaScript::Engine (Perl)`](/docs/demos/engines/duktape)
+
 
 :::note
 
diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js
index 033be83..dbd328c 100644
--- a/docz/docusaurus.config.js
+++ b/docz/docusaurus.config.js
@@ -190,6 +190,9 @@ const config = {
         { from: '/docs/demos/extendscript', to: '/docs/demos/extensions/extendscript/' },
         { from: '/docs/demos/excelapi', to: '/docs/demos/extensions/excelapi/' },
         { from: '/docs/demos/chromium', to: '/docs/demos/extensions/chromium/' },
+        /* cloudata */
+        { from: '/docs/demos/gsheet', to: '/docs/demos/cloudata/gsheet/' },
+        { from: '/docs/demos/airtable', to: '/docs/demos/cloudata/airtable/' },
       ]
     }]
   ]
diff --git a/docz/static/airtable/airpend.png b/docz/static/airtable/airpend.png
new file mode 100644
index 0000000..19978a1
Binary files /dev/null and b/docz/static/airtable/airpend.png differ
diff --git a/docz/static/airtable/pat.png b/docz/static/airtable/pat.png
new file mode 100644
index 0000000..70d08e5
Binary files /dev/null and b/docz/static/airtable/pat.png differ
diff --git a/docz/static/airtable/post.png b/docz/static/airtable/post.png
new file mode 100644
index 0000000..89ca5fb
Binary files /dev/null and b/docz/static/airtable/post.png differ