tutorial··5 min read·WoWSQL Team

Flutter Postgres Backend with the wowsql Dart SDK

Build a Flutter Postgres backend with the wowsql package on pub.dev: install, query tables, sign users in, upload files and subscribe to realtime changes.

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: wowsql on 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 wowsql or flutter pub add wowsql.
  • Main classes: WOWSQLClient (tables), ProjectAuthClient (auth), WOWSQLStorage (files), WOWSQLSchema (DDL, service key only), and client.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.dart creates one WOWSQLClient, one ProjectAuthClient and one WOWSQLStorage, and exposes them to the rest of the app.
  • lib/data/todos_repository.dart holds 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

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.

Flutter SDK docs · Create a free project · Pricing

Flutter Postgres Backend with the wowsql Dart SDK — WoWSQL Blog | WoWSQL