Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

PostgreSQL

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.

What you'll learn

  • Why a Flutter app cannot (and should not) connect directly to a PostgreSQL port 5432
  • How to connect FlutterFlow to Postgres via the native Supabase integration in Settings
  • How to write a Firebase Cloud Function that queries Postgres and returns JSON
  • How to call a Cloud Function from a FlutterFlow API Group
  • How to keep database credentials off users' devices entirely
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read30 minutes (Supabase path) or 60 minutes (Cloud Function proxy path)Database & BackendLast updated July 2026RapidDev Engineering Team
TL;DR

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.

Quick facts about this guide
FactValue
ToolPostgreSQL
CategoryDatabase & Backend
MethodFlutterFlow API Call
DifficultyIntermediate
Time required30 minutes (Supabase path) or 60 minutes (Cloud Function proxy path)
Last updatedJuly 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 API Call

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

1

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).

2

(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.

3

(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).

index.js
1// Firebase Cloud Function — Postgres proxy (index.js)
2const functions = require('firebase-functions');
3const { Pool } = require('pg');
4
5// 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/RDS
9});
10
11exports.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 }
18
19 // Accept a simple action identifier so one function can handle multiple queries
20 const { action, params = [] } = req.body;
21
22 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 };
26
27 if (!queries[action]) {
28 return res.status(400).json({ error: 'Unknown action' });
29 }
30
31 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.

4

(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.

api_call_config.json
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.

5

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.

FlutterFlow Prompt

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.

FlutterFlow Prompt

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.

FlutterFlow Prompt

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.

typescript
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

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.