Airtable — гибрид таблицы и реляционной базы данных с удобным интерфейсом и REST API. Его часто используют, чтобы быстро получить бэкенд без написания серверного кода: каталог, CRM, редакционный план. Ниже — как работает API, когда Airtable подходит, как делать его бэкап и когда пора переезжать на настоящую БД.
Чем Airtable отличается от Notion и обычных таблиц
Airtable позиционируется как «реляционная база данных с интерфейсом таблицы».
От Excel и Google Sheets:
- связи между таблицами (Linked Records — аналог внешнего ключа);
- типизированные поля (дата, число, вложение, а не просто текст);
- формулы и rollup-вычисления по связанным записям;
- REST API из коробки;
- встроенные автоматизации.
От Notion:
- Airtable — база данных, Notion — рабочее пространство с заметками;
- API Airtable проще и предсказуемее для работы с данными;
- длинный контент со структурой (страницы) удобнее хранить в Notion;
- Airtable лучше для структурированных данных, Notion — для смешанного контента. Подробнее — в статье Notion как база данных.
Структура Airtable
- Workspace — верхний уровень, рабочее пространство;
- Base — аналог базы данных (несколько связанных таблиц);
- Table — таблица с полями;
- Record — строка;
- Field — колонка с типом;
- View — представление с фильтрами и сортировкой (данные не меняет).
Типы полей
| Тип | Описание | |---|---| | Single line text | Короткий текст | | Long text | Длинный текст, опционально с форматированием | | Number | Число с форматированием | | Currency | Денежное значение | | Date | Дата и время | | Checkbox | Булево значение | | Single/Multi select | Список значений | | Linked record | Связь с другой таблицей | | Lookup | Значение из связанной записи | | Rollup | Агрегация по связанным записям | | Formula | Вычисляемое поле | | Attachment | Файлы и изображения | | URL, Email, Phone | Специализированный текст | | Rating | Оценка (звёзды) | | Autonumber | Автоинкремент |
Airtable REST API: основы
Аутентификация
Airtable использует Personal Access Tokens (PAT). Создайте токен на airtable.com/create/tokens только с нужными правами (scopes) и только к нужным базам.
# Получить записи таблицы
curl "https://api.airtable.com/v0/$BASE_ID/Products" \
-H "Authorization: Bearer $AIRTABLE_TOKEN"
ID базы виден в URL: airtable.com/appXXXXXXXX/tblXXXXXXXX/...
Получение записей
Формулы и параметры нужно URL-кодировать — проще поручить это curl:
# Фильтр через filterByFormula
curl -G "https://api.airtable.com/v0/$BASE_ID/Posts" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
--data-urlencode "filterByFormula=AND({Status}='Published',{Category}='Tech')"
# Сортировка и лимит
curl -G "https://api.airtable.com/v0/$BASE_ID/Posts" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
--data-urlencode "sort[0][field]=Date" \
--data-urlencode "sort[0][direction]=desc" \
--data-urlencode "maxRecords=10"
# Пагинация: offset из предыдущего ответа
curl -G "https://api.airtable.com/v0/$BASE_ID/Posts" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
--data-urlencode "pageSize=100" \
--data-urlencode "offset=$OFFSET"
Создание записи
curl -X POST "https://api.airtable.com/v0/$BASE_ID/Products" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"Name": "Новый товар",
"Price": 1500,
"Category": "Electronics",
"In Stock": true
}
}'
Обновление
# PATCH — обновить только указанные поля
curl -X PATCH "https://api.airtable.com/v0/$BASE_ID/Products/$RECORD_ID" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fields": {"Price": 1200}}'
Airtable на JavaScript (Node.js)
Официальная библиотека
npm install airtable
const Airtable = require('airtable')
// Только на сервере: токен не должен попасть в браузер
const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN })
.base(process.env.AIRTABLE_BASE_ID)
async function getAllProducts() {
const records = []
await base('Products').select({
filterByFormula: "{Status} = 'Active'",
sort: [{ field: 'Name', direction: 'asc' }]
}).eachPage((pageRecords, fetchNextPage) => {
pageRecords.forEach(record => {
records.push({
id: record.id,
name: record.get('Name'),
price: record.get('Price'),
category: record.get('Category'),
imageUrl: record.get('Image')?.[0]?.url
})
})
fetchNextPage()
})
return records
}
async function createOrder(order) {
return base('Orders').create({
'Customer Name': order.name,
'Email': order.email,
'Product': [order.productId], // Linked record — массив ID
'Status': 'New',
'Date': new Date().toISOString()
})
}
async function updateOrderStatus(recordId, status) {
return base('Orders').update(recordId, { 'Status': status })
}
async function deleteRecord(recordId) {
return base('Products').destroy(recordId)
}
Использование в Next.js Route Handler
// app/api/products/route.ts
import Airtable from 'airtable'
import { NextResponse } from 'next/server'
const base = new Airtable({ apiKey: process.env.AIRTABLE_TOKEN })
.base(process.env.AIRTABLE_BASE_ID!)
export async function GET() {
try {
const records: { id: string; name: unknown; price: unknown }[] = []
await base('Products').select({
filterByFormula: "{Status} = 'Active'",
sort: [{ field: 'Name', direction: 'asc' }]
}).eachPage((pageRecords, fetchNextPage) => {
pageRecords.forEach(record => {
records.push({
id: record.id,
name: record.get('Name'),
price: record.get('Price'),
})
})
fetchNextPage()
})
return NextResponse.json(records)
} catch {
return NextResponse.json({ error: 'Failed to fetch' }, { status: 500 })
}
}
export async function POST(request: Request) {
const body = await request.json()
const record = await base('Orders').create({
'Name': body.name,
'Email': body.email,
'Product': [body.productId],
})
return NextResponse.json({ id: record.id })
}
Airtable на Python
pip install pyairtable
import os
from pyairtable import Api
api = Api(os.environ["AIRTABLE_TOKEN"])
table = api.table(os.environ["AIRTABLE_BASE_ID"], "Products")
# Все записи с фильтром (пагинацию pyairtable делает сам)
records = table.all(
formula="AND({Status}='Active', {Price}<1000)",
sort=["Name"],
)
for record in records:
print(f"{record['fields']['Name']}: {record['fields'].get('Price', 0)} руб.")
new_record = table.create({"Name": "Новый товар", "Price": 999, "Status": "Active"})
table.update(new_record["id"], {"Price": 849})
table.delete(new_record["id"])
# API принимает до 10 записей за запрос — pyairtable сам разбивает список на пачки
table.batch_create([
{"Name": "Товар 1", "Price": 100},
{"Name": "Товар 2", "Price": 200},
{"Name": "Товар 3", "Price": 300},
])
Практические кейсы Airtable как backend
Кейс 1: каталог товаров для лендинга
Схема:
- Products: Name, Price, Description, Images (attachments), Category (single select), Featured (checkbox), Status (single select);
- Categories: Name, Slug, Description.
Next.js получает данные с кэшем на час:
const data = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/Products`, {
headers: { Authorization: `Bearer ${process.env.AIRTABLE_TOKEN}` },
next: { revalidate: 3600 }
})
Учтите: ссылки на вложения, которые возвращает API, временные. Картинки для сайта лучше копировать в своё хранилище, а не вставлять ссылку Airtable напрямую.
Кейс 2: форма обратной связи → CRM в Airtable
// Серверный обработчик формы
async function handleContactForm(formData) {
await base('Leads').create({
'Name': formData.name,
'Email': formData.email,
'Message': formData.message,
'Source': 'Website',
'Status': 'New',
'Created At': new Date().toISOString()
})
}
Заявки с сайта появляются в Airtable, менеджеры работают с ними как в CRM.
Кейс 3: редакционный план и блог
Таблица Posts: Title, Slug, Status (Draft/Review/Published), Author (linked), PublishDate, Tags (multi-select), Content (long text).
def get_published_posts():
records = table.all(
formula="AND({Status}='Published', IS_BEFORE({PublishDate}, TODAY()))",
sort=["-PublishDate"], # минус — сортировка по убыванию
)
return [
{
"slug": r["fields"]["Slug"],
"title": r["fields"]["Title"],
"date": r["fields"]["PublishDate"],
"tags": r["fields"].get("Tags", []),
}
for r in records
]
Ограничения Airtable
Лимиты зависят от тарифа и периодически меняются — ниже ориентиры на момент написания, проверяйте актуальные значения на странице тарифов Airtable.
| Ограничение | Free | Team | |---|---|---| | Записей на базу | 1 000 | 50 000 | | Вложения на базу | 1 ГБ | 20 ГБ | | История ревизий | 2 недели | 1 год |
Важные ограничения API:
- rate limit — 5 запросов в секунду на базу, при превышении API возвращает 429; без кэширования сайт с трафиком упрётся в лимит;
- на бесплатном плане есть ещё и месячный лимит API-вызовов;
- не больше 100 записей за запрос, дальше — пагинация через
offset; - формулы передаются в URL и должны быть закодированы.
Бэкап и экспорт Airtable
Все данные живут в облаке Airtable за рубежом: записи, вложения и история ревизий. Встроенные снимки базы и история изменений помогают откатить ошибку, но хранятся у того же провайдера — при блокировке аккаунта, проблемах с оплатой или случайном удалении базы через API они не помогут.
Ручной вариант — CSV из представления (меню view → Download CSV). Ограничения: одна таблица за раз, только видимые поля, связи выгружаются текстом, вложения — временными ссылками.
Надёжнее — выгрузка через API по расписанию. Токену нужны права data.records:read и schema.bases:read:
# airtable_dump.py — все таблицы базы в JSON и SQLite
import datetime, json, os, sqlite3
from pyairtable import Api
base = Api(os.environ["AIRTABLE_TOKEN"]).base(os.environ["AIRTABLE_BASE_ID"])
stamp = datetime.date.today().isoformat()
db = sqlite3.connect(f"airtable-{stamp}.db")
dump = {}
for table in base.tables():
records = table.all()
dump[table.name] = records
db.execute(f'CREATE TABLE "{table.name}" (id TEXT PRIMARY KEY, created TEXT, fields TEXT)')
db.executemany(
f'INSERT INTO "{table.name}" VALUES (?, ?, ?)',
[(r["id"], r["createdTime"], json.dumps(r["fields"], ensure_ascii=False)) for r in records],
)
db.commit()
db.close()
with open(f"airtable-{stamp}.json", "w", encoding="utf-8") as f:
json.dump(dump, f, ensure_ascii=False, indent=2)
Вложения скачивайте тем же скриптом сразу — ссылки протухают. Запускайте по cron раз в сутки и отправляйте файл за пределы Airtable: в S3 или в dbsend. SQLite-файл удобен тем, что dbsend покажет число записей по таблицам в каждой версии и diff между ними — сразу видно, если кто-то массово удалил строки:
python airtable_dump.py
dbsend backup "airtable-$(date +%F).db" -d "$DBSEND_DATABASE_ID" -e sqlite -l "airtable-$(date +%F)"
Подробнее о хранении JSON в SQLite — в статье JSON как база данных, о версиях и алертах — на странице бэкапы SQLite. Раз в квартал проверяйте, что из дампа можно восстановить данные: залейте пару таблиц в пустую тестовую базу через batch_create — чек-лист в статье как проверить, что бэкап рабочий.
Когда переезжать на SQLite или PostgreSQL: упираетесь в лимит записей или rate limit, нужны транзакции и сложные запросы, персональные данные попадают под 152-ФЗ. Дамп выше — готовая отправная точка для миграции; про выбор СУБД — в статье SQLite, PostgreSQL, MySQL или NoSQL.
No-code инструменты поверх Airtable
- Softr (softr.io) — веб-приложения и клиентские порталы с авторизацией;
- Noloco (noloco.io) — внутренние инструменты;
- Glide (glideapps.com) — мобильные и веб-приложения.
Airtable vs Notion как база данных
| Критерий | Airtable | Notion | |---|---|---| | Реляционные связи | Сильнее (linked records, lookup, rollup) | Есть relations и rollup, но проще | | API | Проще и предсказуемее | Сложная структура ответа | | Длинный контент внутри записи | Только текстовое поле | Полноценные страницы | | Редактирование нетехниками | Хорошо | Отлично | | Автоматизации | Сильные встроенные | Проще | | Оплата из России | Иностранная карта | Иностранная карта |
Безопасность: не раскрывайте токен
Самая частая ошибка — токен Airtable в браузерном JavaScript:
// ОПАСНО — токен виден всем через DevTools
const data = await fetch(`https://api.airtable.com/v0/${BASE_ID}/Table`, {
headers: { Authorization: `Bearer ${token}` }
})
Обращайтесь к Airtable только из серверного кода (Route Handler, API-эндпоинт, серверная функция), а токен держите в переменных окружения.
Частые вопросы
Можно ли использовать Airtable как основную БД приложения?
Для лендингов, MVP, внутренних инструментов и CRM малого бизнеса — да. Для приложений с большим числом пользователей, высокой нагрузкой или транзакциями — нет, переходите на PostgreSQL.
Airtable хранит данные в России?
Нет, данные хранятся за рубежом. Если обрабатываете персональные данные россиян, 152-ФЗ требует их первичной записи и хранения в России — Airtable для этого не подходит. Рассмотрите self-hosted open-source аналоги на российском сервере или обычную БД.
Как сделать полнотекстовый поиск в Airtable через API?
Формулы поддерживают SEARCH() и FIND(), но это медленно и упирается в rate limit. Для нормального поиска выгружайте данные на свой сервер и ищите там: SQLite FTS5, PostgreSQL + pg_trgm или поисковый движок.
Есть ли альтернатива Airtable, которую можно развернуть в России?
Open-source аналоги, которые можно поставить на российский VPS: NocoDB, Baserow, Teable. Под капотом у них обычная БД (часто PostgreSQL), так что бэкапы делаются стандартным pg_dump.
Как сделать Airtable быстрым для сайта с трафиком?
Кэшируйте данные на своём сервере или в Redis и обновляйте кэш по расписанию или по событию из Airtable Automations. Не запрашивайте Airtable при каждом запросе пользователя.
Airtable поддерживает вебхуки?
Да: есть Webhooks API для подписки на изменения базы, а в Automations можно отправить HTTP-запрос при изменении записи. Используйте это для инвалидации кэша или запуска задач в своём приложении.



