FlutterFlow cannot and should not connect directly to a PostgreSQL database — a Flutter app running on a phone should never hold database credentials or open a raw TCP socket on port 5432. The recommended path is FlutterFlow's native Supabase integration, which puts a secure REST layer (PostgREST + RLS) in front of Postgres. If you use a different Postgres host, expose it through a Firebase Cloud Function and call that via a FlutterFlow API Group.
| Fact | Value |
|---|---|
| Tool | PostgreSQL |
| Category | Database & Backend |
| Method | FlutterFlow API Call |
| Difficulty | Intermediate |
| Time required | 30 minutes (Supabase path) or 60 minutes (Cloud Function proxy path) |
| Last updated | July 2026 |
The Right Architecture: REST in Front of Postgres
Let's start with the most important thing to know: a FlutterFlow app compiles to Flutter code that runs on your users' phones and in the browser. If you put a `postgres://username:password@yourhost:5432/db` connection string anywhere in FlutterFlow — App Constants, a Custom Action, or an API Call header — that password ships inside the compiled app where it can be extracted by anyone who downloads your APK. This is not a FlutterFlow limitation; it is a fundamental rule of client-side app development.
The solution is to put a REST layer between FlutterFlow and PostgreSQL. The cleanest option, and the one that requires the least code, is Supabase: it wraps PostgreSQL in a fully-featured platform that includes an auto-generated REST API (PostgREST), Row Level Security (RLS) so users can only see their own data, and an anon key that is safe to use in client apps. FlutterFlow has a built-in native Supabase integration — you connect it in Settings & Integrations and immediately get typed visual query builders without writing any Dart. Supabase's free tier gives you 2 projects and 500MB of PostgreSQL storage; the Pro plan is $25/month and raises the limit to 8GB.
If your PostgreSQL database is hosted elsewhere — on AWS RDS, Neon, a DigitalOcean Droplet, or your own server — you can expose it safely via a Firebase Cloud Function. The function opens an SSL-encrypted connection to Postgres using the `pg` npm package, runs your query, and returns JSON. FlutterFlow calls that function URL through a regular API Group. The database password never leaves your Firebase project's environment configuration.
Integration method
FlutterFlow reaches PostgreSQL through a REST layer, not a direct TCP connection. The easiest path is FlutterFlow's built-in Supabase integration — Supabase runs Postgres under the hood and exposes it via auto-generated PostgREST endpoints secured by Row Level Security. For a non-Supabase Postgres host (Neon, RDS, self-hosted), write a Firebase Cloud Function that queries Postgres server-side and returns JSON; FlutterFlow calls that Function endpoint through an API Group.
Prerequisites
- A PostgreSQL database — either a Supabase project (recommended) or another host such as Neon, AWS RDS, or a self-hosted instance
- For the Supabase path: your Supabase project URL and anon key from Project Settings → API
- For the Cloud Function path: a Firebase project with Blaze (pay-as-you-go) plan to deploy Cloud Functions
- A FlutterFlow project (any plan supports Supabase integration; Custom Actions for other paths require Pro plan for custom code export)
- Basic understanding of SQL table structure and row filtering
Step-by-step guide
Choose your architecture: Supabase native or Cloud Function proxy
Before writing a single line of code, decide which path fits your situation. If you are starting a new project or are willing to use Supabase as your Postgres host, use the native Supabase path — it takes about 30 minutes, requires no backend code, and gives you authentication, Row Level Security, real-time subscriptions, and file storage as bonuses. Open FlutterFlow, go to Settings & Integrations → Supabase, paste your Project URL and Anon Key, and FlutterFlow is connected. Skip ahead to Step 3 if you choose this path. If your PostgreSQL database is already running somewhere else — Neon, RDS, a VPS, or another cloud — you must use the Cloud Function proxy path. You cannot connect FlutterFlow directly to those databases. The Cloud Function path takes about 60 minutes, requires a Firebase Blaze plan (pay-as-you-go), and some familiarity with JavaScript. You'll write a small Node.js function, deploy it to Firebase, and call it from a FlutterFlow API Group. A third path exists on paper: the Dart `postgres` pub.dev package in a Custom Action. This opens a TCP socket to port 5432. It works only on mobile and desktop — the web build throws a 'Unsupported operation: Socket' error. More critically, your database password sits in client code and ships to every user. This path is not recommended for production under any circumstances. We mention it only so you know why to avoid it, not how to use it.
Pro tip: If you don't have a Postgres database yet, create a free Supabase project — you get a managed PostgreSQL instance with a visual table editor, and FlutterFlow integrates with it natively in under 5 minutes.
Expected result: You have decided on your architecture path and know which credentials you will need (Supabase anon key, or Firebase config for the Cloud Function path).
(Supabase path) Connect Supabase in FlutterFlow Settings
If you chose the Supabase path, here is how to connect it. In your FlutterFlow project, open the left navigation and scroll down to 'Settings & Integrations'. Click 'Supabase'. Toggle the switch to enable Supabase. Paste your Supabase Project URL (format: `https://xxxxxxxxxxxxxxxxxxxx.supabase.co`) and your Supabase Anon Key. Both values are in your Supabase dashboard under Project Settings → API. Click 'Get Schema' — FlutterFlow will query your Supabase database schema and import all your table definitions, column names, and types into the FlutterFlow data model. Once the schema is imported, you can see all your tables in the FlutterFlow Data panel. To query a table, add a Backend Query to any widget: select the widget, open the Backend Query section in the right panel, click '+ Add Query', and choose 'Supabase Query'. Pick your table, optionally add filter conditions (e.g. filter `user_id = currentUser.uid` for per-user data), set sort order, and apply pagination. FlutterFlow generates the PostgREST API call automatically — you never write SQL or see the Supabase URL directly. For Row Level Security: go to your Supabase dashboard, open Table Editor → your table → RLS, and enable RLS. Add a policy like 'Users can read their own rows' with filter `auth.uid() = user_id`. FlutterFlow's Supabase calls will respect these policies automatically when the user is authenticated through Supabase Auth.
Pro tip: After enabling RLS on a table, always test with a logged-in user versus an anonymous session to make sure your policies are working. A table with RLS enabled but no policies blocks all access by default.
Expected result: FlutterFlow shows your Supabase tables in the Data panel. Adding a Backend Query to a widget lets you visually configure a Supabase select query with filters.
(Cloud Function path) Write and deploy the Postgres proxy function
If you are using a non-Supabase PostgreSQL host, this step creates the backend bridge. Create a new Firebase Cloud Function in your Firebase project. The function uses the `pg` npm package to open an SSL-encrypted connection to your Postgres database, run a parameterised SQL query, and return the results as JSON. Parameterised queries (using `$1`, `$2` placeholders) are critical — they prevent SQL injection from user-supplied inputs. The database connection string (including username, password, host, port, and database name) must be stored as a Firebase environment config value, never hardcoded. Use the pooler or pgBouncer connection string for your host (Neon, Supabase, and RDS all provide one) rather than the direct connection string. Serverless functions spawn on demand and can exhaust Postgres connection limits quickly if they each open a fresh direct connection; the pooler manages a connection pool so your database doesn't get overwhelmed. You need a Firebase Blaze (pay-as-you-go) plan to deploy Cloud Functions. Blaze charges only for invocations beyond the free tier (2 million calls/month), so for most apps the cost is near zero. Deploy with `firebase deploy --only functions` from the Firebase CLI (run this outside FlutterFlow in your terminal).
1// Firebase Cloud Function — Postgres proxy (index.js)2const functions = require('firebase-functions');3const { Pool } = require('pg');45// Initialise pool at module level (reused across warm invocations)6const pool = new Pool({7 connectionString: functions.config().pg.connection_string,8 ssl: { rejectUnauthorized: false } // required for Neon/Supabase/RDS9});1011exports.queryPostgres = functions.https.onRequest(async (req, res) => {12 res.set('Access-Control-Allow-Origin', '*');13 if (req.method === 'OPTIONS') {14 res.set('Access-Control-Allow-Methods', 'POST');15 res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');16 return res.status(204).send('');17 }1819 // Accept a simple action identifier so one function can handle multiple queries20 const { action, params = [] } = req.body;2122 const queries = {23 listUsers: 'SELECT id, name, email FROM users ORDER BY created_at DESC LIMIT 50',24 getUserById: 'SELECT id, name, email FROM users WHERE id = $1',25 };2627 if (!queries[action]) {28 return res.status(400).json({ error: 'Unknown action' });29 }3031 try {32 const { rows } = await pool.query(queries[action], params);33 return res.status(200).json({ rows });34 } catch (err) {35 console.error(err);36 return res.status(500).json({ error: 'Database error' });37 }38});Pro tip: Set the connection string with: `firebase functions:config:set pg.connection_string="postgres://user:pass@host:5432/db?sslmode=require"` — use the pooler URL, not the direct host.
Expected result: The Cloud Function is deployed and accessible at a URL like `https://us-central1-yourproject.cloudfunctions.net/queryPostgres`. A test POST with `{"action":"listUsers"}` returns rows of data.
(Cloud Function path) Build the API Group in FlutterFlow
With your Cloud Function deployed and returning JSON, connect it to FlutterFlow. In the left navigation, click 'API Calls' → '+ Add' → 'Create API Group'. Name it 'PostgresProxy'. Set the Base URL to your Cloud Function's base URL (e.g. `https://us-central1-yourproject.cloudfunctions.net`). Add a new API Call inside the group. Name it 'ListUsers'. Set the Method to POST and the URL to `/queryPostgres`. Switch to the Body tab, set Body Type to JSON, and set the raw body to `{"action":"listUsers"}`. Click 'Response & Test' and send the request — you should see the JSON array of user rows returned by Postgres. Add JSON Paths to extract the fields you need. If your function returns `{ "rows": [{"id":1,"name":"Alice"}] }`, the path for names is `$.rows[:].name` and for IDs is `$.rows[:].id`. Create a Data Type 'PostgresUser' with `id` (Integer) and `name` (String) fields, map the JSON Paths to those fields, and add a Backend Query to a ListView widget pointing at the ListUsers call. Your Postgres data will appear in the UI.
1{2 "method": "POST",3 "url": "https://us-central1-yourproject.cloudfunctions.net/queryPostgres",4 "headers": {5 "Content-Type": "application/json"6 },7 "body": {8 "action": "listUsers"9 }10}Pro tip: Add an 'Authorization' header check inside your Cloud Function to restrict access to calls coming from your FlutterFlow app — even a simple shared secret prevents others from querying your function endpoint.
Expected result: The FlutterFlow API Group successfully calls the Cloud Function and receives PostgreSQL row data. A ListView binds to the returned rows and displays them.
Map results to Data Types and bind to widgets
Whether you used the Supabase or Cloud Function path, the final step is the same: map the structured JSON response to FlutterFlow Data Types and bind them to widgets. For the Supabase path, FlutterFlow creates these Data Types automatically from your imported schema — you just pick the table in the Backend Query dropdown and the fields appear. Drag a ListView onto the canvas, open Backend Query, choose Supabase Query, select your table, and FlutterFlow handles the rest. Bind widget text fields to the current list item's columns via the '=' binding icon. For the Cloud Function path, create a custom Data Type (left nav → Data Types → + Add) that mirrors your Postgres query result. Add fields for each column you return. In the Backend Query on the ListView, choose 'API Call', select your API Group and call name, and map JSON Paths to Data Type fields in the response mapping section. Then bind ListView item widgets to the Data Type fields as normal. For write operations (INSERT, UPDATE), create a separate API Call that POSTs the payload to your Cloud Function with the appropriate `action` and `params`. Trigger this from an Action Flow on a form submit button. Always validate and sanitise inputs inside the Cloud Function — never concatenate user inputs directly into SQL strings.
Pro tip: For Supabase, use the 'Filter' option in the Backend Query to add `user_id = currentUser.uid` — this fetches only the logged-in user's rows and complements your RLS policy as a client-side safeguard.
Expected result: Your FlutterFlow UI shows real PostgreSQL data. Users see only their own rows (via RLS or function-level filtering). Write operations go through the Cloud Function without exposing credentials.
Common use cases
Task management app backed by Supabase Postgres with per-user data isolation
A founder builds a productivity app where each user sees only their own tasks. Using FlutterFlow's native Supabase integration, they define a `tasks` table in Supabase, enable Row Level Security with a policy that filters by `auth.uid()`, and build a visual query in FlutterFlow that fetches the current user's rows. User signup, login, and data isolation are all handled by Supabase Auth without writing a single line of Dart.
Build a task list screen connected to a Supabase 'tasks' table. Show task name and status for the logged-in user. Let them tap a FloatingActionButton to add a new task via a bottom sheet form.
Copy this prompt to try it in FlutterFlow
Analytics dashboard reading from a self-hosted PostgreSQL reporting database
A SaaS founder has a PostgreSQL reporting database on AWS RDS that aggregates metrics from multiple sources. A Firebase Cloud Function exposes specific read-only queries (total signups, revenue last 30 days) as JSON endpoints. A FlutterFlow admin dashboard calls these endpoints via an API Group and renders charts using FlutterFlow's built-in chart widgets — the RDS password never leaves AWS.
Build an admin dashboard that calls a Cloud Function returning total_signups and revenue_last_30_days from Postgres. Display the values in big-number Stat widgets and a line chart.
Copy this prompt to try it in FlutterFlow
Booking app on Neon serverless Postgres accessed via a REST proxy
A service business uses Neon's serverless PostgreSQL (free tier) to store appointments. A lightweight Firebase Cloud Function accepts booking requests from a FlutterFlow form, inserts the row into Neon with SSL, and returns the booking ID. The client shows a confirmation screen. The Neon connection string with credentials lives only in Firebase Function config.
Create a booking form with Date, Time, and Service fields. On submit, POST to a Cloud Function that inserts the booking into a Neon Postgres table and returns the booking ID. Show a confirmation screen with the ID.
Copy this prompt to try it in FlutterFlow
Troubleshooting
Custom Action using the `postgres` Dart package throws 'Unsupported operation: Socket' on web builds
Cause: The Dart `postgres` package uses `dart:io` TCP sockets, which are not available in Flutter web builds. The package works only on iOS, Android, macOS, and Windows — never web.
Solution: Do not use the Dart `postgres` package for a web-build app. Switch to the Supabase native integration or the Firebase Cloud Function proxy pattern described in this guide. Both are HTTPS-based and work across all platforms.
Cloud Function returns 'no pg_hba.conf entry' or 'SSL connection is required'
Cause: The Cloud Function's `pg` client is connecting without SSL. Managed Postgres hosts like Neon, Supabase, and AWS RDS require SSL on all connections.
Solution: Add `ssl: { rejectUnauthorized: false }` to the Pool constructor in your Cloud Function (for self-signed or managed certs), or use `?sslmode=require` in the connection string. Verify the fix by checking Function logs in the Firebase console after a test call.
1const pool = new Pool({2 connectionString: functions.config().pg.connection_string,3 ssl: { rejectUnauthorized: false }4});Supabase Backend Query returns no rows even though the table has data
Cause: Row Level Security (RLS) is enabled on the table but there is no matching policy for the current user session, or the user is not authenticated and the policy requires `auth.uid()`.
Solution: In Supabase Dashboard → Table Editor → your table → RLS Policies, check that a SELECT policy exists and its condition matches your use-case. For public data, add a policy with `true` condition. For user-specific data, ensure the user is signed in through Supabase Auth in FlutterFlow before the query runs.
Cloud Function exhausts Postgres connections under load and throws 'too many clients'
Cause: Each Cloud Function invocation opens a new direct Postgres connection. Under load, many parallel invocations can exceed the database's max_connections limit (usually 20-100 on free tiers).
Solution: Use the pgBouncer pooler connection string provided by Neon, Supabase, or RDS Proxy instead of the direct connection string. The pooler multiplexes many application connections into fewer database connections. In Supabase, the pooler URL ends in `?pgbouncer=true` and uses port 6543 instead of 5432.
Best practices
- Never paste a PostgreSQL connection string (postgres://user:pass@host/db) into FlutterFlow — it ships inside the compiled app and exposes your database to anyone who extracts it.
- Use Supabase as your Postgres host whenever possible — it gives you a built-in REST layer, Row Level Security, and a native FlutterFlow integration that eliminates the need for custom backend code.
- Store Postgres credentials exclusively in Firebase Cloud Function config (firebase functions:config:set) or Supabase environment secrets — never in source code or FlutterFlow App Constants.
- Always enable SSL on database connections from Cloud Functions — use `ssl: { rejectUnauthorized: false }` for managed certificates on Neon, Supabase, and RDS.
- Use parameterised queries in your Cloud Function (`$1`, `$2` placeholders) to prevent SQL injection — never concatenate user-supplied values directly into SQL strings.
- Use the pooler or pgBouncer connection string in serverless functions to avoid exhausting Postgres connection limits under concurrent load.
- Enable Row Level Security on Supabase tables from day one — it is much easier to add policies progressively than to retrofit security onto a table that already has production data.
- Log Cloud Function errors to the Firebase console and set up alerting on error rates — a broken database connection silently returns an empty response in FlutterFlow if you don't handle errors explicitly.
Alternatives
Firebase is a native FlutterFlow integration with Firestore (NoSQL document store) and real-time subscriptions — choose it over raw PostgreSQL when you want zero backend code and don't need SQL joins or relational integrity.
MySQL follows the same architecture as PostgreSQL for FlutterFlow — proxy via a Cloud Function — but PostgreSQL's richer type system, JSON support, and Supabase native integration make it the better default for new projects.
MongoDB Atlas Data API offers a document-store model over HTTPS without needing a Cloud Function proxy for reads — better than raw PostgreSQL when your data is document-shaped and you want to avoid setting up a backend proxy.
Frequently asked questions
Can I connect FlutterFlow directly to a PostgreSQL database without any backend?
No — and you should not try. A Flutter app runs on the user's device and any connection string or database password you embed in it is extractable by anyone who decompiles the app. The correct approach is to put a REST layer (Supabase, PostgREST, or a Cloud Function) between FlutterFlow and Postgres so the credentials stay server-side.
Is the Dart `postgres` package usable in FlutterFlow at all?
It works as a FlutterFlow Custom Action on iOS, Android, macOS, and Windows builds only — never web. More importantly, using it in production means your database password is compiled into the app binary. We strongly discourage this pattern even for mobile-only apps. The Cloud Function proxy is the production-safe alternative.
Which Supabase plan do I need for FlutterFlow integration?
The free tier (2 projects, 500MB database) is enough to build and launch most apps. The Pro plan at $25/month adds 8GB storage, daily backups, and no project pause after inactivity — recommended for production. The FlutterFlow native integration works on all Supabase plans.
How do I handle user-specific data so each user only sees their own rows?
With Supabase, enable Row Level Security on your table and add a SELECT policy with the condition `auth.uid() = user_id`. Supabase evaluates this policy for every query, so even if a bug in FlutterFlow sends the wrong filter, RLS enforces isolation at the database level. For Cloud Function proxies, include the authenticated user's ID in the request, verify it server-side, and parameterise your WHERE clause with it.
My Firebase Cloud Function works locally but times out in production — what is wrong?
The most common cause is using a direct Postgres connection string (not the pooler) in a serverless function. Direct connections can take several seconds to establish on cold starts, pushing the function over its default 60-second timeout. Switch to the pgBouncer or pooler URL provided by your Postgres host, and initialise the Pool at module level (outside the function handler) so it is reused across warm invocations.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation