A Typst plugin that lets you query SQLite databases at compile time. Write SQL in your .typ files and render the results as tables, charts, or however you like.
Built with Zig, compiled to WASM via Typst's plugin protocol.
This project was created with AI (Claude Code by Anthropic).
#import "sqlite.typ": sqlite, sqlite-table
#let db = sqlite("data.sqlite")
// Query and render as a table
#sqlite-table((db.query)("SELECT name, population FROM cities ORDER BY population DESC"))
// Use raw query results however you want
#let result = (db.query)("SELECT count(*) as n FROM cities")
Total cities: #result.rows.at(0).at(0)Returns a database object with three methods:
| Method | Returns | Description |
|---|---|---|
query(sql) |
{columns: [...], rows: [[...], ...]} |
Execute SQL, get results |
tables() |
("table1", "table2", ...) |
List all table names |
schema(table) |
{columns: [{name, type}, ...]} |
Get column info for a table |
Takes the output of db.query() and renders it. Extra arguments are passed through to Typst's table().
#sqlite-table(
(db.query)("SELECT * FROM cities"),
fill: (x, y) => if y == 0 { gray.lighten(70%) },
)Returns a flat array of cells (headers + data) for use with Typst's table() directly:
#table(
columns: 3,
..query-table(db, "SELECT name, country, population FROM cities")
)The plugin compiles SQLite into a ~150KB WASM module. When Typst compiles your document:
sqlite.typreads the database file as raw bytes via Typst'sread()- The bytes are passed to the WASM plugin which opens them as an in-memory SQLite database (custom read-only VFS)
- SQL queries execute against the in-memory database
- Results come back as JSON, which Typst's
json()parses into native types
All processing happens at compile time. No network, no filesystem access from WASM — just bytes in, JSON out.
Or use mise which handles both:
mise installzig build -Doptimize=ReleaseSmall # or: mise run buildThis produces zig-out/bin/typst_sqlite_zig.wasm.
typst compile example.typ example.pdfThe demo showcases JOINs, CTEs, window functions, self-joins, subqueries, and more:
mise run demomise run test # Run everything (unit + integration)
mise run test-unit # Zig unit tests only (~35 tests for JSON serializer)
mise run test-integration # Build plugin + compile Typst test assertionsGit hooks run tests automatically:
- pre-commit: unit tests
- pre-push: full test suite
src/
main.zig # WASM plugin: VFS, query engine, Typst protocol
json.zig # JSON serializer (fixed-buffer, no allocator)
sqlite.typ # Typst API wrapper
example.typ # Simple usage example
demo.typ # Complex SQL showcase
test.typ # Test runner
test/
test_plugin.typ # Low-level plugin assertions
test_api.typ # sqlite.typ wrapper assertions
tests/
test_json.zig # Unit tests for JSON serializer
gen_testdb.zig # Generates test database (8 tables)
gen_demodb.zig # Generates demo database (5 relational tables)
sqlite3.c/h # SQLite amalgamation (vendored)
libc-stubs/ # Minimal libc for WASM freestanding target
build.zig # Zig build config
mise.toml # Tool versions + task runner
Full SQLite SQL support including:
- SELECT, WHERE, ORDER BY, LIMIT, OFFSET, LIKE
- JOIN (INNER, LEFT, self-joins)
- GROUP BY, HAVING, DISTINCT
- Aggregate functions (COUNT, SUM, AVG, MIN, MAX, GROUP_CONCAT)
- Window functions (RANK, SUM OVER, running totals)
- CTEs (WITH ... AS)
- Subqueries (correlated and uncorrelated)
- CASE expressions
- Date functions (julianday, substr)
- UNION, INTERSECT, EXCEPT
SQLite is in the public domain. The rest of this project is provided as-is.