A Flutter app shouldn't talk to PostgreSQL directly. You'd have to ship database credentials inside the APK or IPA, and anyone can extract them. The usual pattern is a backend in between that exposes a REST API, handles auth and enforces access rules. This tutorial uses WoWSQL's official Dart package, wowsql on pub.dev, to get a managed Postgres backend into a Flutter app.
Key facts
- Package:
wowsqlon pub.dev, version 3.10.0. Dart SDK>=3.0.0 <4.0.0. Works on Flutter mobile, desktop and web, including dart2wasm.- Install:
dart pub add wowsqlorflutter pub add wowsql.- Main classes:
WOWSQLClient(tables),ProjectAuthClient(auth),WOWSQLStorage(files),WOWSQLSchema(DDL, service key only), andclient.realtime.- Query pattern:
client.table('todos').select(['*']).get().- Keys: use the anon key (
wowsql_anon_…) in the app, since it respects Row Level Security. Never ship the service key (wowsql_service_…).- Licence: the package is under the Elastic License 2.0.
Why a BaaS instead of a Postgres driver
Dart has Postgres drivers, and they're fine on a server. In a mobile app they mean:
- database credentials inside the app binary
- your database port open to the internet
- writing your own auth, file storage and change notifications
A Postgres backend-as-a-service puts PostgREST, auth, storage and realtime in front of the database. Your app calls HTTPS endpoints with a public anon key, and the database enforces what that key may do.
1. Create a project and table
Create a free project at app.wowsql.com. Mumbai is the default region; US East 1 is the other option. In the SQL editor, create a table:
create table public.todos (
id uuid primary key default gen_random_uuid(),
title text not null,
done boolean not null default false,
created_at timestamptz not null default now()
);
Use a UUID primary key. WoWSQL's docs note that SERIAL PRIMARY KEY isn't accepted when you create a table through raw SQL, and that WOWSQLSchema.createTable needs a UUID primary key.
Copy the project slug and the anon key from Settings → API.
2. Install the package
flutter pub add wowsql
or in pubspec.yaml:
dependencies:
wowsql: ^3.10.0
3. Initialise the client
import 'package:wowsql/wowsql.dart';
final client = WOWSQLClient(
projectUrl: 'myproject', // resolves to https://myproject.wowsqlconnect.com
apiKey: 'wowsql_anon_your_key_here',
);
projectUrl also accepts the full URL. Don't hard-code keys in source you publish. Pass them in at build time, for example with --dart-define.
4. Read and write rows
// Read: always call select() before get()
final res = await client.table('todos')
.select(['id', 'title', 'done'])
.eq('done', false)
.order('created_at', SortDirection.desc)
.limit(20)
.get();
for (final row in res.data) {
print(row['title']);
}
// Insert
final created = await client.table('todos').insert({'title': 'Ship v1'});
// Update and delete by primary key
await client.table('todos').update(created.id, {'done': true});
await client.table('todos').delete(created.id);
Errors throw WOWSQLException with a statusCode, so a 401 means a bad key and a 404 a missing table.
In a widget, wrap the call in a FutureBuilder, or use whatever state management you already have (Riverpod, Bloc, Provider). The SDK doesn't impose one.
5. Sign users in
final auth = ProjectAuthClient(
projectUrl: 'myproject',
apiKey: 'wowsql_anon_your_key_here',
);
final login = await auth.signIn(
email: 'user@example.com',
password: 'SecurePassword123',
);
if (login.requires2fa) {
// the user has TOTP on: finish with auth.verifyTwoFactorLogin(...)
} else {
print(login.session?.accessToken);
}
The auth client also covers sign-up, password reset, email OTP, magic links, OAuth (Google, GitHub and others, through an authorization URL you open in a browser) and phone OTP. Phone OTP goes through your own Twilio, Fast2SMS or MSG91 account. Since 3.10.0, auth.refreshToken() refreshes sessions.
One thing to know about Row Level Security. In 3.10.0, WOWSQLClient sends your API key as the bearer token on table requests. The docs say the anon key respects RLS. They don't yet show how to run table queries as the signed-in user, which you'd need for per-user policies such as "users see only their own rows". Until that's documented, keep per-user writes behind an Edge Function or your own server that checks the user's access token. Or ask us how to do it on your plan.
6. Upload files
import 'dart:typed_data';
final storage = WOWSQLStorage(
projectUrl: 'myproject',
apiKey: 'wowsql_anon_your_key_here',
);
final bytes = Uint8List.fromList('hello'.codeUnits);
await storage.upload('avatars', bytes,
path: 'users', fileName: 'hello.txt', contentType: 'text/plain');
final url = storage.getPublicUrl('avatars', 'users/hello.txt');
upload with bytes works on every platform, including Flutter web. uploadFromPath and downloadToFile need dart:io, so they don't work on web.
7. Listen for changes
final unsubscribe = client.realtime.subscribe(
table: 'todos',
callback: (change) => print('${change.event} ${change.newRow ?? change.oldRow}'),
);
Enable realtime for the table first (POST /realtime/v1/enable, or from the dashboard). Broadcast and presence are on client.realtime.channel('name'). Payloads are capped at 64 KiB.
Project structure that scales
Keep the SDK behind a small repository layer instead of calling client.table(...) from widgets:
lib/data/wowsql.dartcreates oneWOWSQLClient, oneProjectAuthClientand oneWOWSQLStorage, and exposes them to the rest of the app.lib/data/todos_repository.dartholds the queries (list,add,toggle,remove) and converts rows into your own model classes.- Widgets and state classes talk only to repositories.
This keeps keys and URLs in one place. It also makes tests easy, because WOWSQLClient accepts an optional httpClient, so you can pass a mock in unit tests. And if you ever change backend, you rewrite one folder, not every screen.
Flutter web notes
Add your page origin to the project's CORS allowlist. Since 3.9.4 all HTTP goes through package:http, so auth, data and storage calls work in the browser.
Other Flutter + Postgres routes
- Supabase has a mature Flutter SDK and a larger community. It's a good choice if you don't need an Indian vendor.
- Serverpod gives you a Dart server you write and host yourself, with Postgres behind it.
- PocketBase has a Dart SDK, but it uses SQLite, not Postgres.
WoWSQL suits teams that want managed Postgres in Mumbai with auth, storage and realtime included, from an Indian company, on a free plan or from $25/month.
Related reading
- Indian Firebase alternative: Postgres vs Firestore
- Managed Postgres free tier in India
- Postgres development platform from India
- Flutter SDK docs
Frequently asked questions
Can Flutter connect directly to PostgreSQL?
It can with a Dart driver, but you'd ship database credentials in the app. Use a backend API instead, such as a Postgres BaaS or your own server.
What is the WoWSQL Flutter package called?
wowsql on pub.dev. Install it with flutter pub add wowsql and import package:wowsql/wowsql.dart.
Does the wowsql package work on Flutter web?
Yes. From 3.9.4, all calls use package:http. File helpers that need dart:io (uploadFromPath, downloadToFile) don't work on web, so use upload and download with bytes.
Which key goes in my Flutter app?
The anon key (wowsql_anon_…). The service key bypasses Row Level Security and belongs on a server only.
Is the Dart SDK open source?
It's source available under the Elastic License 2.0, which isn't an OSI open-source licence.