- Cloud (dashboard)
- Self-host (YAML)
Your policy is the per-database grid on a project. Each database has a Table permissions matrix and a Guardrails card; saving hot-reloads the running engine. A new database starts read-only.
Read-only by default
The starting point is always the same: all reads allow, all writes deny. A fresh Midplane is immediately safe — an agent can explore and query, but can’t change anything until you grant a write, one table at a time. See the policy engine for how each decision is made. A policy has two top-level keys, both optional:
Use
databases or the top-level table_access, not both — when a databases block is present, the top-level keys and DATABASE_URL are ignored. Every key, type, and default is in the policy schema reference.
The three access levels
Every table resolves to one of three levels. A query is denied if any table it touches fails its required permission.read_write grants row writes, not whole-table destruction. A no-WHERE DELETE/UPDATE and any DROP/TRUNCATE/ALTER are denied by the dangerous-statement guardrails even on a read_write table — so a write grant can’t be escalated into a wipe. For exactly what each level permits, how names resolve, and the full guardrail set, see table access and guardrails.Set a default, then override per table
Pick the level for any table you don’t list, then add the exceptions:default: read— the recommended start. Reads everywhere, writes nowhere until granted.default: deny— locked down. Only the tables you list are visible. Use it when the agent should see a named allowlist and nothing else.
- Cloud (dashboard)
- Self-host (YAML)
In the database’s Table permissions matrix, set Default access to
read. Each row is a table; each column is a level. Add a row per exception and pick its level — overrides win over the default. Set feature_flags to read_write and audit_log to deny.SELECT * FROM users— allowed (default: read).DELETE FROM users WHERE id = 1— denied (usersis read-only).INSERT INTO feature_flags (name) VALUES ('beta')— allowed (feature_flagsisread_write).SELECT * FROM audit_log— denied (audit_logisdeny, no access at all).
Schema-qualified names
Table keys may be bare (feature_flags) or schema-qualified (public.users, stripe.charges). Schema-qualified keys match first, so a canonical policy still matches agent SQL that uses bare names in public.
public — Midplane pins each connection’s search path to public, pg_catalog. Tables outside public must be schema-qualified in both the policy and the agent’s SQL (for example app_data.orders: read plus FROM app_data.orders).
Serve multiple databases
One Midplane endpoint can front several databases (0.2.0+). Replace the top-level table_access with a databases array; each entry has its own connection URL and policy.
string
required
Short identifier the agent uses to pick the database — lowercase, starts with a letter, up to 32 characters.
string
required
The connection DSN. Supports
${ENV_VAR} interpolation, so DSNs and passwords stay in environment variables, not the file. An unset referenced variable fails loudly at boot.string
default:"postgres"
Only
postgres — omit it.object
The same per-table policy as above, scoped to this database.
query takes a database argument and a list_databases tool appears. A single database changes none of this.
- Cloud (dashboard)
- Self-host (YAML)
Add each database’s URL under the project; each gets its own policy grid. The project exposes one endpoint, and the agent picks a database by name.
Guardrails
Guardrails are categorical blocks that fire regardless of table access — a table you grantedread_write still can’t be wiped or altered. Both ship on:
- Unqualified writes — a
DELETE/UPDATEwith noWHEREclause (the whole-table write). - Schema changes (DDL) —
DROP,TRUNCATE,ALTER.
guardrails block. Switching one to Allow is opting into a risk, not a neutral default. Two deeper invariants — multi-statement queries and unparseable SQL — are always denied and have no toggle anywhere. See what Midplane blocks for the full rule set.
Test before you ship
- Cloud (dashboard)
- Self-host (YAML)
The editor’s test panel runs your policy through the engine’s real decision path and shows a pass/fail result without touching your database. Saving then hot-reloads the running engine — no restart, no redeploy — and the edit is audited with the teammate who made it.
Examples
Complete, copy-pasteable policies for the shapes that come up most. None set aguardrails block, so the dangerous-statement guardrails stay on — add one only to relax them.
SaaS product database
Reads open by default, a couple of operational tables writable, the audit log locked out entirely.Feature flags only
Read everything, write exactly one table.Multiple databases
A production database plus a read-only analytics warehouse through one endpoint (0.2.0+). DSNs come from environment variables via ${ENV_VAR}, so no secrets live in the file.
Reference
Policy schema
Every key, type, and default.
Table access & guardrails
How each level behaves, plus the guardrails and rules that deny dangerous shapes.
Author with the CLI
Scaffold, validate, lint, and test policies.