Grist: The Self-Hosted Spreadsheet That Thinks Like a Database
Spreadsheets are easy to start but hard to maintain. Databases are powerful but require a developer. Grist sits in between: it has the familiar row-column interface of a spreadsheet, but it stores data in a relational model with proper references, computed columns, and access controls.
Photo by Egor Komarov on Unsplash
If you're running Airtable for internal tools and want to stop paying per seat, or you've outgrown Google Sheets but don't want to build a full app, Grist is worth evaluating.
What Makes Grist Different
Most spreadsheet alternatives add a UI skin over a database (Baserow, NocoDB). Grist goes the other direction — it starts with the spreadsheet mental model and adds database capabilities:
- Tables, not sheets: Each Grist document has multiple related tables
- Reference columns: One table can reference records in another, like a foreign key
- Python formulas: Computed columns use Python, not a proprietary formula language
- Linked views: A selection in one widget filters data shown in another
- Access rules: Row-level and column-level visibility controls
- Version history: Every change is tracked and revertible
The result is something that non-technical users can understand while still handling the kind of data modeling that would normally require a developer.
Docker Deployment
services:
grist:
image: gristlabs/grist:latest
container_name: grist
environment:
APP_HOME_URL: https://grist.yourdomain.com
GRIST_SESSION_SECRET: your-random-secret-here
# Optional: SMTP for invites
GRIST_EMAIL_SENDER: [email protected]
SMTP_HOST: smtp.yourdomain.com
SMTP_PORT: 587
SMTP_USER: [email protected]
SMTP_PASSWORD: smtp-password
volumes:
- grist_data:/persist
ports:
- "8484:8484"
restart: unless-stopped
volumes:
grist_data:
Access the instance at http://localhost:8484. On first launch, create an admin account and then sign in.
For production, put Grist behind a reverse proxy (Nginx, Traefik, Caddy) with HTTPS. Set APP_HOME_URL to your public URL — Grist uses this for sharing and embed links.
Creating Your First Document
Grist organizes data into documents, each containing multiple tables. Think of a document as a database file and tables as, well, tables.
Example: Project Tracker
Create a document with two tables:
Projects table:
| Column | Type |
|---|---|
| Name | Text |
| Status | Choice (Active, Done, Paused) |
| Due Date | Date |
| Owner | Reference → People |
People table:
| Column | Type |
|---|---|
| Name | Text |
| Text | |
| Team | Text |
The Owner column in Projects is a Reference to People. Grist enforces this relationship and lets you do lookups across tables in formulas.
Like what you're reading? Subscribe to Self-Hosted Weekly — free weekly guides in your inbox.
Python Formulas
This is Grist's standout feature. Every computed column uses Python:
# Days until due date
from datetime import date
(rec.DueDate - date.today()).days
# Full name from first + last
rec.FirstName + " " + rec.LastName
# Sum of related records (like VLOOKUP, but cleaner)
sum(r.Hours for r in rec.Tasks.lookupRecords(Project=rec.id))
# Conditional
"Overdue" if rec.DueDate < date.today() and rec.Status != "Done" else rec.Status
Python gives you loops, list comprehensions, date arithmetic, and imports from the standard library. It's a real programming environment inside a spreadsheet.
Linked Views
Grist's killer feature for dashboards: linked sections. When you click a row in one table, other sections on the same page filter automatically.
A typical layout:
- Left panel: Projects table (full list)
- Right top: Tasks linked to the selected project
- Right bottom: Activity log linked to the selected project
This creates a drill-down UI without writing any JavaScript. Non-technical users can navigate complex relational data through a well-designed page layout.
Access Rules
Grist has granular row-level and column-level permissions:
# Only show rows belonging to the current user
user.Email == rec.AssignedTo
# Hide salary column from non-managers
user.Role != "Manager"
# Allow editing only if status is Draft
rec.Status == "Draft"
Access rules run as filter conditions — not just UI controls, but enforced at the data layer. External embed viewers see only what the rules allow.
Grist vs. Alternatives
| Feature | Grist | Airtable | NocoDB | Baserow |
|---|---|---|---|---|
| Self-hostable | ✓ | ✗ | ✓ | ✓ |
| Formulas | Python | Proprietary | Limited | Limited |
| Reference columns | ✓ | ✓ | ✓ | ✓ |
| Row-level permissions | ✓ | ✓ (paid) | ✓ | ✓ (paid) |
| Linked views | ✓ | ✓ | ✗ | ✗ |
| API access | ✓ | ✓ | ✓ | ✓ |
| Price (self-hosted) | Free | — | Free | Free |
NocoDB and Baserow are more database-forward — they connect to PostgreSQL/MySQL and manage tables there. Grist has its own storage model, which makes it simpler to set up but less flexible for existing database schemas.
REST API
Every Grist document has a REST API that external tools can use:
# List records in a table
curl -H "Authorization: Bearer YOUR-API-KEY" \
"https://grist.yourdomain.com/api/docs/DOC_ID/tables/Projects/records"
# Create a record
curl -X POST -H "Authorization: Bearer YOUR-API-KEY" \
-H "Content-Type: application/json" \
-d '{"records":[{"fields":{"Name":"New Project","Status":"Active"}}]}' \
"https://grist.yourdomain.com/api/docs/DOC_ID/tables/Projects/records"
This makes Grist useful as a lightweight data store for scripts and automation tools. Pair it with n8n or Pipedream for no-code integrations.
When to Use Grist
Grist shines for:
- Internal business tools — inventory, CRM, project tracking
- Complex spreadsheets that are outgrowing Excel/Google Sheets
- Multi-user data where you need permissions per row or column
- Teams that need Python instead of learning a proprietary formula language
Skip Grist if you need to connect to an existing PostgreSQL database (use NocoDB or Baserow), or if you need real-time collaboration at Notion's scale.
