You found the Postgres connection string in the Supabase dashboard under Project Settings, and you are about to paste it into your Lovable app so a server action can run a query. The string starts with postgresql://postgres:[YOUR-PASSWORD]@db.xxxxxxxx.supabase.co:5432/postgres. The question is whether that URI is safe to put anywhere a Lovable build can reach, and what the right place for it actually is.
Short answer
The Postgres connection string in the Supabase dashboard is the master credential for your database. It logs in as the postgres role (or a privileged role you defined), and that role bypasses every Row Level Security policy and every PostgREST permission. It must never appear in the Lovable frontend code, in a VITE_ environment variable, in a chat prompt to the AI, or in a .env file the deployed bundle can read. The supported place for it is the Supabase Edge Function secret store, where it lives as a DATABASE_URL secret read at runtime by Deno on the server side. Supabase's guide to connecting to Postgres documents the three connection modes; only persistent servers and Edge Functions should ever see them. The Lovable client should use the publishable anon key, as described in the Supabase API keys reference.
What you should know
- The connection string carries postgres-role credentials. Anyone who reads it gets full SQL access across every schema, with RLS bypassed.
- VITE_ and NEXT_PUBLIC_ prefixes ship the value to the browser. Vite inlines every VITE_ variable into the JavaScript bundle at build time, where DevTools can read it.
- The anon (publishable) key is designed for the client. It only allows operations RLS policies explicitly permit, so it can live in the Lovable bundle.
- The service_role (secret) key bypasses RLS too. Same blast radius as the connection string for the PostgREST surface, and the same rule applies: server only.
- Lovable's Secrets feature stores values in Supabase Edge Function secrets. Pasting DATABASE_URL there is the documented path.
- Lovable's chat history is not a secret store. Anything pasted into the prompt is logged by Lovable and replayed into AI context across messages.
Why is the Postgres connection string different from the anon key?
The connection string and the anon key target two different surfaces of the same database. The connection string opens a raw Postgres connection on port 5432 (or 6543 for the pooler), logging in as a Postgres role that has unrestricted access to data unless you have created custom roles with limited GRANTs. The anon key authenticates against PostgREST, the HTTP layer that sits in front of Postgres and runs each request as the anon Postgres role with RLS enforced.
That means the two values carry very different risk. According to the Supabase API keys documentation, publishable keys (the new name for anon) are explicitly designed for client-side exposure because access is guarded by Postgres through the built-in anon and authenticated roles. The connection string carries no such restraint. Connecting with it gives the caller everything: drop tables, read every row, change every password, write any file the postgres role can reach.
For a Lovable app, the practical implication is simple. The Supabase URL and the anon key go into supabase-js in the frontend. The connection string never does. The moment it appears in a Vite env var, a hardcoded constant, or a chat message asking Lovable to use this database URL for a new feature, it is one git push away from being public.
How does Lovable expect database secrets to be handled?
Lovable's official integration treats secrets as a server-side concern. The Lovable Supabase integration documentation describes the connection through OAuth with the Supabase dashboard, and the Lovable security documentation states that API keys and other secrets cannot be stored safely in client-side code and that values pasted as secrets are kept in the Supabase Edge Function secret manager, encrypted on the backend.
When you connect Lovable to Supabase, the platform wires supabase-js into the frontend with the Supabase URL and the publishable anon key. Those two values are designed to be public. When you ask Lovable to build a feature that needs a server-side operation (a Stripe webhook, a privileged database write, an admin endpoint), the chat prompts you to add a secret. That secret lives in Edge Function secrets, accessible to Deno code via Deno.env.get, never reachable from the React bundle.
The mistake to avoid is treating a chat message as a secret channel. Anything pasted into Lovable's chat is logged and stays in the AI's context. Pasting the Postgres connection string into a prompt that says use this to add a query leaks the string into Lovable's logs, even if the generated code is correct.
Where does the connection string actually need to live?
In one place: the Supabase Edge Function secret store. You set it once through the Supabase dashboard at Project Settings, Edge Functions, Secrets, or via the Supabase CLI with supabase secrets set DATABASE_URL='postgresql://...'. Edge Functions read it at cold start with Deno.env.get(). The Lovable frontend calls the Edge Function over HTTPS using supabase.functions.invoke('name'), and the URL never crosses into the browser.
For long-running backends outside Lovable (a Node service on Fly, a worker on Render), the same string belongs in the host's secret manager, not committed to the repo. The Supabase docs on connecting to Postgres note that direct connections are best for persistent servers, transaction mode is best for serverless edge functions, and session mode is the fallback when IPv4 is needed. Choosing the right mode reduces pool exhaustion, but it does not change where the credential lives.
The check that catches most leaks is a grep over the build output. After the production build runs, search the dist/ folder for the literal string postgres:// and for any password fragment from the connection string. A clean build returns zero matches. A leak shows the credential pre-bundled into a chunk-XXX.js file.
What does a safe Edge Function read of the connection string look like?
A minimal example, written in TypeScript for the Deno runtime that powers Supabase Edge Functions:
import { Client } from "https://deno.land/x/[email protected]/mod.ts";
Deno.serve(async (req) => {
const databaseUrl = Deno.env.get("DATABASE_URL");
if (!databaseUrl) {
return new Response("server not configured", { status: 500 });
}
const client = new Client(databaseUrl);
await client.connect();
try {
const result = await client.queryObject<{ count: bigint }>(
"select count(*) from public.orders where user_id = $1",
[req.headers.get("x-user-id") ?? ""],
);
return Response.json({ count: Number(result.rows[0].count) });
} finally {
await client.end();
}
});
Three details matter. The credential is read from Deno.env, not imported from a config file in the repo. The function trusts the request only after checking an authentication header (in real code, a JWT verified through supabase.auth.getUser, abbreviated here for readability). And the SQL uses parameterised queries; concatenating user input into the SQL string brings SQL injection back even after RLS has stopped being the gatekeeper. The Supabase guide on Edge Function secrets describes the supabase secrets set command and confirms that values injected this way are not visible to the project's frontend code.
How do the credential types compare for a Lovable app?
| Credential | Where it belongs | RLS bypass | Visible in browser bundle? |
|---|---|---|---|
| Supabase URL (https://xxxx.supabase.co) | Frontend, Edge Function | No | Yes, by design |
| Anon / publishable key | Frontend, Edge Function | No | Yes, by design |
| Service_role / secret key | Edge Function secret only | Yes | Never |
| Postgres connection string (postgresql://...) | Edge Function secret only | Yes (privileged role) | Never |
| Supabase JWT secret | Edge Function secret only | N/A (signs tokens) | Never |
The table reflects what the Supabase API keys reference and the Supabase database security guide describe. Two columns matter most for Lovable apps. The RLS bypass column shows which credentials make Row Level Security irrelevant. The visibility column shows which ones can safely cross the network boundary into the user's browser. A credential that bypasses RLS and ends up in the browser bundle behaves like a public superuser key for the database. That is the failure mode underlying the cluster of Lovable-app leaks reported in early 2025, and indirectly behind the chained vulnerabilities the Hacktron team described in their SupaPwn write-up, where a privileged Postgres surface let researchers escalate well beyond a single tenant.
What to watch out for
A few patterns turn a one-line fix into a public breach.
- Pasting the connection string into Lovable's chat. The string is now in Lovable's chat history, in the model's context window for future messages, and in any analytics Lovable retains. Rotate the database password the moment this is spotted.
- Using VITE_DATABASE_URL or NEXT_PUBLIC_DATABASE_URL. Both prefixes inline the value into the static bundle. Vite's documentation states explicitly that only VITE_-prefixed env vars are exposed to client-side code; that is exactly the wrong scope for a database URI.
- Connecting from a Node script inside src/. Even if the script is meant to seed data at build time, Vite may bundle the file or its sibling files. Keep migrations and seeders in a separate folder excluded from the build, and run them locally.
- Trusting a generated Edge Function without reading it. Lovable's AI sometimes inlines a hardcoded password while generating an Edge Function. Always check the produced .ts file for the literal connection string before deploying.
- Assuming the pooler URL is safer. Both pooler ports (5432 and 6543) accept the same credential. The pooler reduces connection counts, not exposure.
- Forgetting to rotate after a leak. Resetting the database password through Project Settings, Database, Reset database password invalidates the old connection string. The same applies to the JWT secret when service_role tokens were exposed.
For builders who want an external automated read of the compiled Lovable bundle for hardcoded database URIs, service_role keys, or other server-only credentials before pointing a custom domain at the app, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-launch scanning of AI-generated web apps for these patterns aligned with OWASP MASVS expectations for backend authentication.
Key takeaways
- The Postgres connection string is the master credential for your Supabase database. It bypasses RLS, PostgREST, and every policy you wrote in the dashboard.
- The supported place for it is the Supabase Edge Function secret store, read at runtime as Deno.env.get("DATABASE_URL"). It never belongs in Lovable frontend code.
- The anon (publishable) key is the credential that lives in the browser. The combination of Supabase URL plus anon key plus correctly configured RLS is the boundary the public reaches.
- Grep the production bundle for postgres:// and for the database password before deploying. A clean build returns zero matches; a leak shows the secret pre-inlined into JavaScript.
- Some teams outsource the pre-launch credential sweep across the compiled Lovable bundle to platforms like PTKD.com (https://ptkd.com), which reports hardcoded database URIs, exposed service_role keys, and other server-only credentials before the app reaches production traffic.




