# PostgreSQL

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 30 minutes (Supabase path) or 60 minutes (Cloud Function proxy path)
- Last updated: July 2026

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

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

## Before you start

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

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

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

```
// Firebase Cloud Function — Postgres proxy (index.js)
const functions = require('firebase-functions');
const { Pool } = require('pg');

// Initialise pool at module level (reused across warm invocations)
const pool = new Pool({
  connectionString: functions.config().pg.connection_string,
  ssl: { rejectUnauthorized: false } // required for Neon/Supabase/RDS
});

exports.queryPostgres = functions.https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return res.status(204).send('');
  }

  // Accept a simple action identifier so one function can handle multiple queries
  const { action, params = [] } = req.body;

  const queries = {
    listUsers: 'SELECT id, name, email FROM users ORDER BY created_at DESC LIMIT 50',
    getUserById: 'SELECT id, name, email FROM users WHERE id = $1',
  };

  if (!queries[action]) {
    return res.status(400).json({ error: 'Unknown action' });
  }

  try {
    const { rows } = await pool.query(queries[action], params);
    return res.status(200).json({ rows });
  } catch (err) {
    console.error(err);
    return res.status(500).json({ error: 'Database error' });
  }
});
```

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

```
{
  "method": "POST",
  "url": "https://us-central1-yourproject.cloudfunctions.net/queryPostgres",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "action": "listUsers"
  }
}
```

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

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

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

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

Prompt example:

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

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

Prompt example:

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

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

Prompt example:

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

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

```
const pool = new Pool({
  connectionString: functions.config().pg.connection_string,
  ssl: { rejectUnauthorized: false }
});
```

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

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

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/postgresql
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/postgresql
