Skip to main content

Notion SDK vs Airtable API vs Google Sheets API: Spreadsheet and Database APIs (2026)

·PkgPulse Team

TL;DR

Notion SDK provides programmatic access to Notion databases, pages, and blocks — query databases, create pages, manage content, build integrations with the Notion workspace. Airtable API turns Airtable bases into a backend — CRUD operations, formulas, attachments, views, webhook automations, relational data. Google Sheets API treats spreadsheets as a database — read/write cells, formatting, charts, widely used for quick data backends and reporting. In 2026: Notion SDK for content and knowledge base automation, Airtable API for relational data and app backends, Google Sheets API for spreadsheet automation and reporting.

Key Takeaways

  • Notion: @notionhq/client ~300K weekly downloads — databases, pages, blocks, rich content
  • Airtable: airtable ~200K weekly downloads — relational data, views, formulas, attachments
  • Google Sheets: googleapis ~5M weekly downloads — cells, ranges, formatting, charts
  • Notion excels at rich content (pages, blocks, embeds)
  • Airtable excels at relational data (linked records, views)
  • Google Sheets is the most accessible (everyone knows spreadsheets)

Notion SDK

Notion SDK — Notion API client:

Query database

import { Client } from "@notionhq/client"

const notion = new Client({ auth: process.env.NOTION_TOKEN })

// Query a database:
const response = await notion.databases.query({
  database_id: "abc123...",
  filter: {
    and: [
      {
        property: "Status",
        select: { equals: "Published" },
      },
      {
        property: "Tags",
        multi_select: { contains: "react" },
      },
    ],
  },
  sorts: [
    { property: "Date", direction: "descending" },
  ],
  page_size: 10,
})

// Parse results:
response.results.forEach((page: any) => {
  const title = page.properties.Title.title[0]?.plain_text
  const status = page.properties.Status.select?.name
  const date = page.properties.Date.date?.start
  console.log(`${title}${status} (${date})`)
})

Create page

import { Client } from "@notionhq/client"

const notion = new Client({ auth: process.env.NOTION_TOKEN })

// Create a page in a database:
const page = await notion.pages.create({
  parent: { database_id: "abc123..." },
  properties: {
    Title: {
      title: [{ text: { content: "React 19 Released" } }],
    },
    Status: {
      select: { name: "Draft" },
    },
    Tags: {
      multi_select: [
        { name: "react" },
        { name: "release" },
      ],
    },
    Date: {
      date: { start: "2026-03-09" },
    },
    Downloads: {
      number: 25000000,
    },
  },
  // Page content (blocks):
  children: [
    {
      object: "block",
      type: "heading_2",
      heading_2: {
        rich_text: [{ text: { content: "Overview" } }],
      },
    },
    {
      object: "block",
      type: "paragraph",
      paragraph: {
        rich_text: [{
          text: { content: "React 19 introduces new features..." },
        }],
      },
    },
    {
      object: "block",
      type: "code",
      code: {
        rich_text: [{ text: { content: "npm install react@19" } }],
        language: "bash",
      },
    },
  ],
})
import { Client } from "@notionhq/client"

const notion = new Client({ auth: process.env.NOTION_TOKEN })

// Update page properties:
await notion.pages.update({
  page_id: "page-123...",
  properties: {
    Status: { select: { name: "Published" } },
    Date: { date: { start: new Date().toISOString().split("T")[0] } },
  },
})

// Search across workspace:
const search = await notion.search({
  query: "react",
  filter: { property: "object", value: "page" },
  sort: { direction: "descending", timestamp: "last_edited_time" },
})

search.results.forEach((result: any) => {
  console.log(result.id, result.url)
})

// Append blocks to page:
await notion.blocks.children.append({
  block_id: "page-123...",
  children: [
    {
      object: "block",
      type: "callout",
      callout: {
        rich_text: [{ text: { content: "Updated with latest data!" } }],
        icon: { emoji: "📊" },
      },
    },
  ],
})

Airtable API

Airtable — relational database API:

Query records

import Airtable from "airtable"

const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN })
  .base(process.env.AIRTABLE_BASE_ID!)

// Query records:
const records = await base("Packages")
  .select({
    filterByFormula: "AND({Status} = 'Active', {Downloads} > 1000000)",
    sort: [{ field: "Downloads", direction: "desc" }],
    maxRecords: 10,
    view: "Published",
  })
  .all()

records.forEach((record) => {
  console.log({
    id: record.id,
    name: record.get("Name"),
    downloads: record.get("Downloads"),
    tags: record.get("Tags"),
    status: record.get("Status"),
  })
})

Create and update records

import Airtable from "airtable"

const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN })
  .base(process.env.AIRTABLE_BASE_ID!)

// Create records (batch up to 10):
const created = await base("Packages").create([
  {
    fields: {
      Name: "react",
      Description: "UI library for building interfaces",
      Downloads: 25000000,
      Tags: ["frontend", "ui"],
      Status: "Active",
      Website: "https://react.dev",
    },
  },
  {
    fields: {
      Name: "vue",
      Description: "Progressive JavaScript framework",
      Downloads: 5000000,
      Tags: ["frontend", "ui"],
      Status: "Active",
    },
  },
])

// Update records:
await base("Packages").update([
  {
    id: "rec123...",
    fields: {
      Downloads: 26000000,
      "Last Updated": new Date().toISOString(),
    },
  },
])

// Upsert (update or create):
await base("Packages").update(
  [{ fields: { Name: "react", Downloads: 26000000 } }],
  { performUpsert: { fieldsToMergeOn: ["Name"] } }
)

Linked records and formulas

import Airtable from "airtable"

const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN })
  .base(process.env.AIRTABLE_BASE_ID!)

// Create with linked records:
await base("Versions").create([
  {
    fields: {
      Version: "19.0.0",
      Package: ["rec123..."],  // Link to Packages table
      "Release Date": "2026-03-09",
      "Breaking Changes": true,
    },
  },
])

// Query with linked records expanded:
const packages = await base("Packages")
  .select({
    fields: ["Name", "Downloads", "Latest Version"],
    // "Latest Version" is a lookup/rollup from Versions table
  })
  .all()

// Computed fields are read-only:
// - Formulas: calculated server-side
// - Rollups: aggregate linked records
// - Lookups: pull fields from linked records

Webhooks and automations

// Airtable webhook (receive changes):
// POST /api/webhooks/airtable

app.post("/api/webhooks/airtable", async (req, res) => {
  const { base, table, record, action } = req.body

  if (action === "create" && table === "Packages") {
    // New package added — trigger analysis:
    await analyzePackage(record.fields.Name)
  }

  if (action === "update" && record.fields.Status === "Published") {
    // Published — deploy content:
    await deployContent(record)
  }

  res.sendStatus(200)
})

Google Sheets API

Google Sheets API — spreadsheet access:

Read data

import { google } from "googleapis"

const auth = new google.auth.GoogleAuth({
  keyFile: "service-account.json",
  scopes: ["https://www.googleapis.com/auth/spreadsheets"],
})

const sheets = google.sheets({ version: "v4", auth })

// Read a range:
const response = await sheets.spreadsheets.values.get({
  spreadsheetId: process.env.SHEET_ID!,
  range: "Packages!A2:E",  // Skip header row
})

const rows = response.data.values || []
rows.forEach(([name, downloads, version, tags, status]) => {
  console.log({ name, downloads: Number(downloads), version, tags, status })
})

// Read multiple ranges:
const multi = await sheets.spreadsheets.values.batchGet({
  spreadsheetId: process.env.SHEET_ID!,
  ranges: ["Packages!A2:E", "Downloads!A2:C", "Metadata!A1:B10"],
})

multi.data.valueRanges?.forEach((range) => {
  console.log(range.range, range.values?.length, "rows")
})

Write data

import { google } from "googleapis"

const sheets = google.sheets({ version: "v4", auth })

// Append rows:
await sheets.spreadsheets.values.append({
  spreadsheetId: process.env.SHEET_ID!,
  range: "Packages!A:E",
  valueInputOption: "USER_ENTERED",
  requestBody: {
    values: [
      ["react", 25000000, "19.0.0", "frontend,ui", "Active"],
      ["vue", 5000000, "3.5.0", "frontend,ui", "Active"],
    ],
  },
})

// Update specific cells:
await sheets.spreadsheets.values.update({
  spreadsheetId: process.env.SHEET_ID!,
  range: "Packages!B2",  // Downloads column, row 2
  valueInputOption: "RAW",
  requestBody: {
    values: [[26000000]],
  },
})

// Batch update multiple ranges:
await sheets.spreadsheets.values.batchUpdate({
  spreadsheetId: process.env.SHEET_ID!,
  requestBody: {
    valueInputOption: "USER_ENTERED",
    data: [
      { range: "Packages!B2", values: [[26000000]] },
      { range: "Packages!B3", values: [[5500000]] },
      { range: "Metadata!B1", values: [[new Date().toISOString()]] },
    ],
  },
})

Formatting and sheets management

import { google } from "googleapis"

const sheets = google.sheets({ version: "v4", auth })

// Create new sheet:
await sheets.spreadsheets.batchUpdate({
  spreadsheetId: process.env.SHEET_ID!,
  requestBody: {
    requests: [
      {
        addSheet: {
          properties: {
            title: "March 2026 Report",
            gridProperties: { rowCount: 1000, columnCount: 10 },
          },
        },
      },
    ],
  },
})

// Format cells (bold header, background color):
await sheets.spreadsheets.batchUpdate({
  spreadsheetId: process.env.SHEET_ID!,
  requestBody: {
    requests: [
      {
        repeatCell: {
          range: { sheetId: 0, startRowIndex: 0, endRowIndex: 1 },
          cell: {
            userEnteredFormat: {
              backgroundColor: { red: 0.23, green: 0.51, blue: 0.96 },
              textFormat: { bold: true, foregroundColor: { red: 1, green: 1, blue: 1 } },
            },
          },
          fields: "userEnteredFormat(backgroundColor,textFormat)",
        },
      },
    ],
  },
})

Feature Comparison

FeatureNotion SDKAirtable APIGoogle Sheets API
Data modelPages + databasesTables + recordsSheets + cells
Rich content✅ (blocks, embeds)❌ (fields only)❌ (cells only)
Relational data✅ (relations)✅ (linked records)❌ (manual)
Formulas✅ (database formulas)✅ (powerful)✅ (spreadsheet)
Attachments✅ (files, images)✅ (attachments)❌ (links only)
Webhooks✅ (automations)✅ (Apps Script)
Real-time
Rate limits3 req/s5 req/s60 req/min
Row limitUnlimited50K/base (free)10M cells
TypeScript✅ (@types)
AuthAPI tokenAPI tokenOAuth2/Service Account
Free tierGenerous1,200 records15 GB Drive
PricingPer-userPer-seatFree/Workspace

When to Use Each

Use Notion SDK if:

  • Building integrations with Notion workspaces
  • Need rich content management (pages, blocks, embeds)
  • Building knowledge bases or content pipelines
  • Want to automate Notion databases programmatically

Use Airtable API if:

  • Need a relational database with a visual UI
  • Building app backends with linked records and views
  • Want powerful formulas and automations
  • Need attachments and file management

Use Google Sheets API if:

  • Need spreadsheet automation and reporting
  • Building data pipelines that output to spreadsheets
  • Want the most familiar interface for non-technical users
  • Need integration with the Google Workspace ecosystem

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on @notionhq/client v2.x, airtable v0.12.x, and googleapis v140.x.

Compare API client and developer tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.