Airtable Is a Great Product That Costs Too Much for What It Is
Airtable is genuinely useful: spreadsheet interface, multiple view types, API access, automations, collaboration. But $20/user/month for the Pro plan — which you need for anything beyond toy usage — adds up fast. And then your data lives in their database, and your team’s workflows are locked to their platform.
NocoDB is the “wait, this is just a UI on top of a SQL database” realization, packaged into a product. You connect it to your existing Postgres, MySQL, SQLite, or SQL Server database, and you get Airtable’s view types, a RESTful API auto-generated from your tables, webhooks, and team collaboration — on hardware you control.
It’s not a perfect Airtable replacement. But for internal tooling, home lab dashboards, and anything where “we’re paying Airtable $X00/month to put a pretty face on a database” is the current situation, it’s a very solid option.
What NocoDB Actually Is
Underneath the spreadsheet interface, NocoDB is a Rails + Vue application that talks to your SQL database and presents it as a collaborative grid. Here’s what that means practically:
- Your data stays in your database, in normal SQL tables
- NocoDB doesn’t own your schema — you connect to an existing table, it reads the columns
- If you stop using NocoDB, your data is still there, in your database, unmodified
- You can use NocoDB as a frontend while other applications hit the same database directly
This is the important architectural distinction from Airtable: NocoDB is a view layer, not a data layer. Your Postgres database doesn’t know or care that NocoDB exists.
Docker Compose Setup
# docker-compose.yml
version: '3'
services:
nocodb:
image: nocodb/nocodb:latest
restart: unless-stopped
ports:
- "8080:8080"
environment:
NC_DB: "pg://db:5432?u=nocodb&p=supersecret&d=nocodb"
NC_AUTH_JWT_SECRET: "your-random-jwt-secret-here"
# Optional: disable signup for private instances
NC_DISABLE_TELE: "true" # Disable telemetry
depends_on:
- db
volumes:
- nocodb-data:/usr/app/data
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: nocodb
POSTGRES_USER: nocodb
POSTGRES_PASSWORD: supersecret
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
nocodb-data:
postgres-data:
docker compose up -d
# Access at http://localhost:8080
# Create your first admin account in the UI
NocoDB uses its own Postgres database for metadata (views, users, API tokens) while connecting to your target databases separately.
Connecting to an Existing Database
This is where NocoDB earns its keep. You have a Postgres database full of actual data. NocoDB reads it directly.
In the NocoDB UI:
- Create a new Base → Connect to existing database
- Choose database type: PostgreSQL, MySQL, SQLite, etc.
- Enter connection details:
Host: your-postgres-server
Port: 5432
Database: your_app_db
User: readonly_user
Password: password
Schema: public
NocoDB will introspect the database, find all tables, and create a workspace where each table becomes a sheet. Column types are inferred from SQL types:
varchar/text→ Text fieldinteger/bigint→ Number fieldboolean→ Checkbox fieldtimestamp→ DateTime fieldjsonb→ JSON field (displayed as formatted JSON)
Foreign keys are recognized and become link fields — you can browse related records inline.
For production use, create a read-only database user for the NocoDB connection if you don’t need write access from the UI:
CREATE USER nocodb_reader WITH PASSWORD 'readonlypass';
GRANT CONNECT ON DATABASE your_app_db TO nocodb_reader;
GRANT USAGE ON SCHEMA public TO nocodb_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO nocodb_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO nocodb_reader;
Views: Grid, Gallery, Kanban, Calendar
NocoDB’s multiple view types are what make it more useful than a plain database GUI.
Grid View (Default)
Standard spreadsheet. Filter, sort, group, inline editing. This is what you use for bulk data management.
# Add a filter in the UI:
Filter: status = 'active' AND created_at > '2024-01-01'
# Group by a column:
Group by: department
# Sort:
Sort by: created_at (descending)
Gallery View
Cards with images. Useful for product catalogs, media libraries, contact lists. Pick an “attachment” column as the card image, a text column as the title.
Practical use: you have a table of products with a thumbnail_url column. Gallery view makes it look like an actual product catalog instead of a spreadsheet.
Kanban View
Columns based on a single-select field. Drag cards between columns to update the field value.
-- In your database
ALTER TABLE tasks ADD COLUMN status VARCHAR(50) DEFAULT 'todo';
-- With values: 'todo', 'in_progress', 'review', 'done'
NocoDB turns this into a kanban board where dragging a card updates the status column in the database. Your application code reading that same table sees the updated status immediately.
Calendar View
Requires a date column. Shows records on a calendar based on their date value. Useful for scheduling, event management, due dates.
Auto-Generated REST API
Every table in NocoDB automatically gets a REST API. No configuration required.
# Get your API token from: User settings → API tokens
# List records
curl -X GET \
-H "xc-token: YOUR_API_TOKEN" \
"http://localhost:8080/api/v1/db/data/noco/YOUR_BASE_ID/YOUR_TABLE_NAME?limit=25&offset=0"
# Filter and sort
curl -X GET \
-H "xc-token: YOUR_API_TOKEN" \
"http://localhost:8080/api/v1/db/data/noco/YOUR_BASE_ID/tasks?where=(status,eq,todo)&sort=-created_at"
# Create a record
curl -X POST \
-H "xc-token: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "New task", "status": "todo", "assignee": "alice"}' \
"http://localhost:8080/api/v1/db/data/noco/YOUR_BASE_ID/tasks"
# Update a record
curl -X PATCH \
-H "xc-token: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "done"}' \
"http://localhost:8080/api/v1/db/data/noco/YOUR_BASE_ID/tasks/ROW_ID"
# Swagger UI available at:
# http://localhost:8080/api/v1/db/meta/projects/YOUR_BASE_ID/swagger
The Swagger UI documents every endpoint automatically. This is genuinely useful for letting team members explore the API without reading documentation.
Webhooks
NocoDB supports webhooks for table events: record created, updated, deleted.
In the UI: Table → Toolbar → Webhook → Add new webhook
{
"title": "Notify Slack on new task",
"event": "after.insert",
"condition": true,
"url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
"method": "POST",
"body": {
"text": "New task created: {{record.title}} assigned to {{record.assignee}}"
}
}
Webhooks support template variables from the record. Combine with n8n or a simple webhook receiver for more complex workflows.
NocoDB vs Baserow vs Grist
Three self-hosted Airtable alternatives, three different philosophies:
| Feature | NocoDB | Baserow | Grist |
|---|---|---|---|
| Connect to existing DB | Yes | No (manages its own) | No |
| Spreadsheet + formulas | Basic | Basic | Full spreadsheet formulas |
| Offline support | No | No | Yes (desktop app) |
| API auto-generation | Yes | Yes | Yes |
| Python/JS scripting | No | No | Yes |
| Setup complexity | Low | Low | Medium |
| Best for | Existing database UI | Greenfield data apps | Power spreadsheet users |
Choose NocoDB when you have an existing database and want to put a UI on it without migrating data.
Choose Baserow when you’re starting fresh and want a simpler, more Airtable-like experience with better UI polish.
Choose Grist when you need actual spreadsheet formulas, Python scripting, or a desktop app for offline use.
Performance with Large Datasets
NocoDB is a frontend — it queries your database on demand. Performance depends on your database:
-- Add indexes for columns you filter/sort frequently
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC);
CREATE INDEX idx_tasks_assignee ON tasks(assignee);
-- For full-text search on text columns
CREATE INDEX idx_tasks_title_gin ON tasks USING gin(to_tsvector('english', title));
With proper indexing, NocoDB handles hundreds of thousands of rows without trouble. Without indexing, a filter query on an unindexed column in a table with 100k rows will be painfully slow — NocoDB will execute the query and wait, same as any other client.
The NocoDB pagination defaults to 25 records per page. For large tables, always use the API with proper limit and offset parameters rather than loading everything.
Team Collaboration
NocoDB supports multiple users with role-based access:
- Owner: Full access, manage team
- Creator: Create/modify tables, views, automations
- Editor: Add/edit records
- Commenter: View + comment (like Google Docs comments)
- Viewer: Read-only
Invite team members at the Base level or the Table level. Different tables can have different access levels.
For internal tooling: give the ops team Editor access to the tickets table, give engineers Viewer access to the customer data table. The same NocoDB instance, the same database, different levels of access per team.
When NocoDB Is the Right Tool
It makes the most sense for:
- Existing databases you want a quick frontend on: Connect NocoDB to your app’s database for a management interface without building one
- Small team internal tooling: Expense tracking, project management, content calendars — anything where “spreadsheet in a database” is the right abstraction
- Home lab dashboards: Visualize data from your sensors, media server, or monitoring tables without writing a custom frontend
It’s less appropriate for:
- Replacing your application’s data layer: NocoDB is a UI, not an ORM. Your app should still talk to the database directly.
- Complex relational data with many joins: The interface gets unwieldy with heavily normalized schemas
- High-traffic read APIs: The auto-generated API is fine for internal use; for production APIs with real traffic, use a proper API layer
The $0/month price point covers a lot of its limitations.