Data · We run it ourselves
Managed PostgreSQL with an authentication service, file storage and a generated HTTP API in front of it, addressable directly from a browser.
This is not an entry about the database. PostgreSQL is covered on its own page and we run it unmanaged elsewhere; what Supabase sells is the apparatus around it — hosting, a token issuer, object storage, and an API key that ships inside the client. The whole arrangement rests on one Postgres feature doing its job, which is why row-level security is not a hardening step here but the load-bearing wall.
01
What is being bought is operational, not relational. Postgres itself costs nothing; connection pooling, point-in-time recovery, a restore somebody else has actually rehearsed and a JWT issuer we did not write are the line items. On a product with eleven tables and one paying tenant, building that apparatus costs more than the product it carries, and that is the trade this entry is about.
02
The publishable key is compiled into the client and visible to anyone who opens the network tab. That is the design rather than a leak: the key names the project and nothing else, and every question of who may read which row is answered by a policy attached to the table. It inverts the usual arrangement — instead of a server trusted because it is the only thing holding a secret, there is a database that trusts nothing and re-checks every statement.
03
The pattern that makes it useful is a write-only table. A lead capture table enables row-level security, permits an anonymous INSERT with a check of true, and refuses SELECT with a policy of false. The form then works from a page with no server behind it, and anyone replaying the same key against the same endpoint gets an empty set rather than the list of names. The rule is a dozen lines of SQL sitting next to the data, so a second application built against that table later cannot widen it by forgetting.
04
Row-level security is a PostgreSQL feature and not a Supabase one, which is precisely what keeps the exit open. The schema, the policies and the migration history are ordinary SQL and move to any Postgres, managed or not. Auth and storage do not move, and naming which half is portable before the first table is written is the difference between a platform decision and a trap.
05
Session handling, password reset, OAuth callbacks and refresh rotation are solved problems with a long tail of ways to be subtly wrong. The part that earns the dependency is that the issued token arrives inside the database as auth.uid(), so the identity that signed in is the identity the row policy tests. Identity is not re-derived in a middle tier where two services can come to different conclusions about the same user.
06
Storage objects are rows in a table governed by the same policy language, so a private bucket is expressed the way a private table is expressed. A second access-control system with its own vocabulary is a second place for the rule to be right in one and wrong in the other.
07
Schema is kept as files under version control rather than as a state somebody clicked into the dashboard — a migrations directory in one project, one .sql file per table in another. A schema whose history exists only in a web console cannot be reviewed in a diff, replayed onto a fresh project, or rolled back after a bad afternoon.
An entry that cannot point at something you can open, run or read does not compile. That is a property of the type, not a promise in a paragraph.
Invariant
A policy is a predicate the planner has to satisfy for every row it touches, so a rule that joins to a membership table or calls a function per row turns an index scan into a per-row cost — and the query stays correct while getting slower, which is the hardest class of regression to attribute, because nothing ever returns a permission error to point at. Past a few thousand tenants with nested policies the honest move is to stop expressing the rule in row-level security and put it behind a service that owns the query. The exit is also only half open: schema, policies and migrations are ordinary Postgres and move anywhere, but auth and storage are the vendor's, so leaving costs whatever was built on those two.