A typical Zonai project looks like this:
my_app/
├── zonai.yaml # Project config — paths, server settings, build targets
├── pubspec.yaml
├── .env # Local secrets (gitignored)
├── .env.prod # Production secrets (gitignored)
├── lib/
│ └── src/
│ ├── schemas/ # Table definitions (one file per table)
│ ├── config/ # AppConfig — secrets, SMTP, JWT settings
│ ├── rules/ # Authorization rules
│ ├── operations/ # Custom SQL generation (optional)
│ ├── extensions/ # Lifecycle hooks
│ ├── rate_limit/ # Per-table throttling
│ ├── crons/ # Scheduled background jobs
│ └── email_templates/ # Mustache HTML email templates
├── .zonai/
│ ├── migrations/ # Generated SQL migration files (commit these)
│ ├── executables/ # Compiled worker binaries (gitignored)
│ └── data/ # SQLite database and uploaded photos (gitignored)
└── build/ # Production bundle — only after `zonai build`
Source Directories (lib/src/)#
These are the only directories you write code in.
schemas/ — one Dart file per table. Each file defines the row type, the table class, and calls
table() or authTable() to register it. All files here are auto-discovered.
config/ — one or more Dart files exporting an AppConfig main()
function. Contains JWT secret, SMTP configuration, base URL, and other runtime settings. Compiled into the config worker.
rules/ — authorization files. <table>_table_rules.dart
controls operation-level access; <table>_row_rules.dart controls per-row access. Unoverridden operations default to denied.
operations/ — custom SQL generation. Optional — most tables don't need a file here. Defaults already cover CRUD
and live streams (/db/stream*). Add a file to override SQL, add JWT claims to an auth table, or define non-standard operations. See
Streaming.
extensions/ — lifecycle hooks that run before and after mutations and auth events. Optional — add a file when a table needs side effects (sending email, creating related rows, audit logging).
rate_limit/ — per-table throttle configuration. Optional — a default policy applies to any table without a file.
crons/ — scheduled background job classes. Each file defines a class extending
CronJob. Optional.
email_templates/ — HTML files with Mustache variables used by email.send.custom(...). Built-in templates are auto-generated here on first use.
Generated Directories (.zonai/)#
Zonai manages these. Don't edit their contents by hand.
.zonai/migrations/ — SQL files generated by zonai db migrate generate.
Commit these to version control — they are the source of truth for your schema history.
.zonai/executables/ — compiled worker binaries. Gitignore this directory. Rebuilt by
zonai serve (auto) and zonai compile (manual).
.zonai/zonai — optional AOT project binary used when serve --release
runs from the app root (dev/prod without shipping build/).
.dart_tool/zonai/ — generated entrypoints (project_main.dart, worker stubs). Regenerated on compile/serve.
.zonai/data/ — the SQLite database file and uploaded photo files. Gitignore this. Never commit database files or user uploads.
The build/ Directory#
Created by zonai build. Contains everything needed to run on a production server: the project-linked
zonai binary (ops/rules in-process + full CLI), worker executables, migration files, email templates, and
zonai.yaml. Ship this entire directory to your server.
Naming Conventions#
Files in each worker directory are auto-discovered by convention:
| Type | Filename Pattern | Example |
|---|---|---|
| Table rules | <table>_table_rules.dart |
task_table_rules.dart |
| Row rules | <table>_row_rules.dart |
task_row_rules.dart |
| Operations | <table>_operations.dart |
user_operations.dart |
| Extensions | <table>_extensions.dart |
item_extensions.dart |
| Rate limits | <table>_rate_limits.dart |
item_rate_limits.dart |
No registration is needed — any .dart file in the correct directory is automatically included.
Live queries ship with every table. Clients should use zonai_client db.listen or GET /db/stream* instead of polling. Details: Streaming (Live Queries).
