Features · Database
A real database, already wired.
Managed Postgres with no database account to open. On a paid plan, your managed databases are pooled over your apps: ask for one when you deploy, and one action creates it, connects it and only then builds, so the migrations your build runs reach it in the very first build. The Free plan includes one for your app, with its first deploy where the Free plan runs the app’s server code and otherwise once it is live, kept while the app is online.
Take my bakery app live, with a database.
tofu · setFramework nextjs
confirmed
tofu · deploy database: true
managed database ready · Postgres
DATABASE_URL + 5 more · write-only
build ready
HTTP 200 · publicly reachable
Live at https://corner-bakery-3f9a1c07b2e4.trytofu.app
Create, connect, then build. In that order, in one step.
Choose a database on the deploy step — a checkbox in the dashboard, database: true from your agent, deploy --database from a shell. Each database counts toward the databases your plan includes, so it is never a default: only your yes, for that app, creates one.
Create the database
A Postgres database of its own, in the region you choose: Americas, EMEA or APAC.Wait until it is healthy
For up to 2 minutes. If it is not connected by then, no build is submitted, and the answer names the state it is in.Write the connection settings
Onto your app’s hosting project: the connection as write-only values, and the database’s public certificate beside it. On a first deploy, Tofu creates that project here.Build, with the database there
Only now. When your build runs your migrations, they run against the new database in the very first build, before the app goes live.
A database created earlier from the Database page is connected the same way by your next deploy, before that deploy builds.
Wired in, never shown.
Tofu writes the connection settings straight into your hosting provider’s sensitive, write-only environment, and the database’s public certificate beside them as a plain value. Tofu itself keeps the names and nothing else, and no service-role key is used.
| Variable | Who receives it |
|---|---|
| DATABASE_URL | Every app — the connection string, through the provider’s transaction pooler. |
| SUPABASE_URL | Every app — the database project’s public address. |
| SUPABASE_ANON_KEY | Every app — its public key. |
| SUPABASE_CA_CERT | Every app — the database server’s public root certificate, so your code can check it is talking to your database. Not a secret. |
| ‹prefix›SUPABASE_URL‹prefix›SUPABASE_ANON_KEY‹prefix›SUPABASE_PUBLISHABLE_KEY | Next.js (NEXT_PUBLIC_), Vite (VITE_) and Astro (PUBLIC_) apps — the public address and key again, under the name their browser bundle reads. |
Every other preset — a Python, Go or JavaScript server app included — gets the server-side names only, because Tofu does not guess a browser prefix.
Confirm your app’s framework before the database connects: the browser names are chosen from it at that moment, and are not added afterwards.
On the Free plan, if your app is on Tofu’s shared static hosting, its pages reach the database straight from the browser, so it gets only the public address and key under VITE_, and no DATABASE_URL or certificate. Otherwise it receives these as on a paid plan.
Your migrations, run by your build.
The database starts empty on purpose: the schema belongs to the repository that defines it. Put your migration in the build command and every deploy brings its schema along — no credential is copied anywhere, and the schema cannot fall behind the code it shipped with.
From 2026-10-30 Supabase no longer gives a new table in public to the roles the app’s pages use, so the migration that creates a table has to grant it: select to anon only for what visitors who are not signed in may read, select, insert, update, delete to authenticated, and the same to service_role if server code uses it. A view needs its own grant select, created with security_invoker = true so the rows underneath stay protected by their access rules.
When a migration your build runs fails, the build fails and the previous version keeps serving. Statements it had already applied stay applied.
Where your Database page says so, Tofu applies the files in your supabase/migrations folder itself, before each update. If one of them fails, Tofu stops that update before building it, and any version already online keeps running.
Structure travels; data does not. The migrations your build runs reach this database. Rows that live only on your machine do not, unless a migration inserts them.
On the Free plan, if your app is on Tofu’s shared static hosting, its build runs no migrations, so its tables are set up outside the build, as the Database page shows for your app. Otherwise its build can run them, as on a paid plan.
{
"scripts": {
"build": "prisma migrate deploy && next build"
}
}drizzle-kit migrate. Supabase CLI: supabase db push --db-url "$DATABASE_URL". Plain SQL: the runner your repository already has.A table editor, not a SQL console.
The Database page lists the tables in your public schema and pages through their rows — without handing anyone a console.
Read
Every table in your database, 25 rows a page
Your app’s own schemas, and the ones the database service keeps for it — its sign-in accounts among them, view-only, passwords and tokens hidden until you press Show — with each column’s type, the primary key and whether row-level security is on. A row count is the planner’s estimate, and says so.
Write
Off until you switch it on
Writing is a switch per app. Each insert, change or delete is then one row, confirmed on its own — and a row that changed meanwhile is reported, not overwritten.
Never
No schema changes, no SQL
The editor cannot create, alter or drop anything. Rows leave it one page at a time on your screen, or a whole table at a time, up to a size limit, as a CSV file you download.
Yours alone
In your dashboard, not your agent
No agent can read or write rows. The editor connects as the database’s own admin role, so row-level security does not limit it — and the panel says so.
Your data, and your way out.
Moved to your own Supabase organization
On request, the database moves to a Supabase organization you own — a transfer arranged with you and reviewed, in the same region, rather than a button.
The connection string, when you need it
For a local tool or a one-off data fix, Show the database key rebuilds it for you, the owner, and records that it did. No agent can receive it.
Deleted when you say so
Deleting a database is an owner’s action: you type the app’s name, and the database, its schema and its data are removed, and the delete makes no copy.
By design.
Browser names for Next.js, Vite and Astro only. Other presets get the server-side names, and Tofu has run no Python or Go app against a managed database end to end.
No password rotation. Nothing in the dashboard changes the database password; a suspected leak is a support case.
Tofu designs no tables of its own, and has no SQL console. Your app’s tables come from its own migrations: where your Database page says so, Tofu applies the files in supabase/migrations before each update; otherwise a migration step in your build, or your own machine with the database key, applies them. The missing SQL console is policy: uploaded source is untrusted input, and one operator credential spans every managed database.
Local data stays local. Rows that exist only on your machine never reach this database unless a migration inserts them.
No tables or policies of Tofu’s own. The database starts empty; your migrations create the tables, and the row-level-security policies your app relies on.
How many databases you get depends on your plan. The number is for all your apps together. The pricing page lists which plans include databases and how many.
Paste one message. Online in 10 minutes.
Your app, its database, your domain and your sign-in — from the agent you already use.