Docs
Database
An app on a paid plan can have its own managed Postgres: a Supabase project Tofu creates, connects and pays for, created in the same action as the deploy, so a migration step in your build, if it has one, runs against the new database before the app goes live. The Free plan's app can have one too. An app on Tofu's shared static hosting gets it once it is live, and its build runs no migrations; any other Free app gets it as on a paid plan.
Agent tools
databaseCreatedatabaseStatusdatabaseReconciledatabaseDeletedatabaseRestore
One action with the deploy
An app on a paid plan can have one managed Supabase project, created from Database in the dashboard or by an agent's databaseCreate — any framework, Next.js, Vite or Astro included. The app does not have to be deployed first: choose the database on the deploy confirm step — the checkbox, or database: true / deploy --database for an agent — and one action does the rest, in this order:
Create
Tofu creates the database and waits for it.
Connect
It saves the connection settings on the app's hosting project.
Build
Only then is the build submitted, so a migration step in your build, if it has one, runs against the new database before the app goes live.
Tofu designs no tables of its own. 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. Until something does, the database stays empty.
A database created earlier is connected the same way by the next deploy, before that deploy builds.
On the Free plan, the app's one managed database is connected the same way, unless the app is on Tofu's shared static hosting. There it is added if Tofu builds the app from its code, once the app is live, from Database with databaseCreate or with the next deploy after that. Tofu keeps it while the app is in your account, and does not archive it for being unused. On Tofu's shared static hosting, the app's build runs no migrations, so its tables are set up outside the build: the Database page shows how for your app, and the database key there lets you create them from your own machine. Any other Free app's build can run them, as on a paid plan.
What your app receives
The connection variables reach the app from that project's sensitive environment: DATABASE_URL, SUPABASE_URL and the public URL and key. Beside them, as a plain value rather than a secret, is SUPABASE_CA_CERT: the database server's public root certificate (below). Next.js, Vite and Astro also get the URL and key under their own browser-facing prefix; every other preset, a Python, Go or JavaScript server app included, gets the server-side names only, because Tofu does not guess a prefix.
| Preset | Variables |
|---|---|
| Every app | DATABASE_URL, SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_CA_CERT |
| Next.js | The same, plus NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY |
| Vite | The same three under VITE_ |
| Astro | The same three under PUBLIC_ |
They are managed variables, read-only in the dashboard's Keys & settings. The control database keeps names and flags only, and Tofu writes no connection string to a log.
On the Free plan, the pages of an app on Tofu's shared static hosting reach the database straight from the browser, so the app gets only the three VITE_ names, which hold the public URL and key, and no DATABASE_URL or certificate. Any other Free app gets the variables above, as on a paid plan.
The database’s certificate
DATABASE_URL asks for an encrypted connection, and a client that also checks which server it reached needs the certificate that server chains to. Node.js does not ship it, so without it the connection fails with SELF_SIGNED_CERT_IN_CHAIN — and so does a build that runs your migration. Tofu writes it on every app with a managed database as SUPABASE_CA_CERT: Supabase's public root certificate, in PEM, on production and preview. It is public, never the password. An app whose database was connected before Tofu wrote it gets it at its next deploy.
node-postgres checks the server because the URL's sslmode tells it to, and the URL's TLS settings replace an ssl object passed beside it, so a ca next to the unchanged URL is not enough. Take them off the URL first:
import { rootCertificates } from "node:tls";
import pg from "pg";
// The URL's TLS settings would replace the ssl object below.
const url = new URL(process.env.DATABASE_URL);
for (const name of ["ssl", "sslmode", "sslcert", "sslkey", "sslrootcert", "uselibpqcompat"]) {
url.searchParams.delete(name);
}
// Unset, Node would fail with ERR_INVALID_ARG_TYPE, which does not name it.
const ca = process.env.SUPABASE_CA_CERT;
if (!ca) throw new Error("SUPABASE_CA_CERT is not set: Tofu writes it at the app's next deploy");
export const pool = new pg.Pool({
connectionString: url.toString(),
ssl: { ca: [...rootCertificates, ca], rejectUnauthorized: true },
});For a build step you would rather not edit — drizzle-kit migrate on node-postgres, say — write the certificate to a file and point Node at it for that step:
printf '%s' "$SUPABASE_CA_CERT" > /tmp/supabase-ca.pem
NODE_EXTRA_CA_CERTS=/tmp/supabase-ca.pem drizzle-kit migratelibpq tools — psql, pg_dump — check the certificate and the host name with sslmode=verify-full and that file as their root:
PGSSLROOTCERT=/tmp/supabase-ca.pem psql "${DATABASE_URL%%\?*}?sslmode=verify-full"No certificate file is needed in your upload. Never turn the check off: rejectUnauthorized: false encrypts the connection without knowing which server is on the other end.
Your schema, your migrations
Put one of these in your build command, ahead of the framework build, and every build runs your migrations against this database:
prisma migrate deploy
drizzle-kit migrate
supabase db push --db-url "$DATABASE_URL"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.
On the Free plan, these work as on a paid plan, except on Tofu's shared static hosting: there the app's build runs none of these, so its tables are set up outside the build, as the Database page shows for your app.
A failed migration fails the build and blocks publication — the previous version keeps serving — but statements already accepted stay applied. That is the whole of the local-to-cloud promise, and its boundary: structure travels, data does not — the migrations your build runs reach this database, rows that live only on your own machine do not.
Tofu runs your app's own migrations only where your Database page says so, and has no SQL console. Its read is read-only — recorded migrations and the base tables of every schema your app owns, public and any it created — reported as empty, populated, unavailable or unknown: a failed read is never an empty schema, and an app that keeps its tables in a schema of its own is not empty. An app that creates its tables when it first runs reads empty until its first request.
Seeing your data
The Database page carries a table editor for every schema of your app's database, one at a time, picked above the list: the tables your app keeps, in public and any schema it created, and the ones the database service runs for your app, such as auth, which holds its sign-in accounts. It shows their rows 25 at a time, and — only while you have switched writing on for that app — makes one inserted, changed or deleted row per confirmed request in your app's own tables. Writing is off by default.
- The database service's schemas are view-only — its migration record and its extensions among them — because a change there can break sign-in, file storage or your app's migrations, so Tofu's server refuses every write to them (and to views) before it connects. A column there, or in a view, that holds a password hash, a token or a secret stays hidden until you press Show — a view you made over your sign-in accounts hides what they hide — and Show reads that column for the page on screen and leaves one audit line naming the table and the column, never a value. Every page is read without changing anything, even a view that would.
- No schema changes and no raw SQL. Rows leave the editor one page at a time on screen, or one whole table at a time with Download as CSV: a spreadsheet file of up to 50,000 rows, recorded with who downloaded which table, never a value. A hidden column stays out of the file unless you showed it.
- The editor connects as the database's own admin role, so the row-level security policies your app adds for its users do not limit what it shows — the panel says so.
- Every write attempt leaves one audit line that names the table, never a value.
The same page lists the database's daily backups — one a day, kept for seven days — with Ask for a restore beside each complete one: a person at Tofu puts the whole database back to that copy and emails you, and everything saved after it is lost.
Yours to take or delete
The connection string. The dashboard action Show the database key is the one owner-only exception: it rebuilds the value from the recorded attempt and writes one audit line per call. No agent can reach it, and rotation is not implemented.
Moving it. A managed database is kept for you when you leave, and its transfer to your own Supabase organization is arranged on request.
Deleting it. databaseDelete, or the Database page, removes the database — the data, the schema and the Supabase project behind it — permanently, and the delete keeps no copy. A copy Tofu already holds of a running database, such as the archive you brought it back from, is untouched; deleting an archived database deletes every copy Tofu holds for the app. It asks for the app's own name, typed back.
An archived database. databaseRestore puts it back into a new database Tofu runs and pays for, and databaseArchiveDownload hands you short-lived links to its stored copy.
An app with a managed database cannot be deleted until the database is deleted or transferred: projectDelete refuses it and names the step.
What Tofu does not do to your database
Design your tables. Your tables come from your app’s 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, applies them. Tofu has no SQL console.
Move your rows. Structure travels through the migrations your build runs; local data stays on your machine.
Rotate the password. Credential rotation is not built.
How many databases you get depends on your plan. The number is for all your apps together: when every included database is in use, deleting a database on an app’s Database page frees a place for another app. The pricing page lists which plans include databases and how many.
说说你想做什么 app,或者带上 agent 已经做好的 app。Tofu 帮你上线。
任何 coding agent 都能用
© 2026 Tofu
trytofu.ai
本页由英文翻译;如有内容冲突,请以英文原版为准。