Ship on Australian-owned Postgres.
WattleDB gives you a fully-managed PostgreSQL database — plus an instant REST API, object storage, backups and point-in-time recovery — running entirely on Australian-owned infrastructure in Sydney. This guide takes you from zero to a connected, production database in about ten minutes.
Sign in to the console
Everything starts at the console — console.wattledb.com.au. It's where you create databases, grab connection strings, run SQL, and manage billing.
WattleDB is in private beta, so public sign-ups are closed. If you have an invite, use Log in with your email and password. First time in, you'll confirm your email address before the workspace opens.
Create your first database
From Databases → New database, pick a plan and click create. WattleDB provisions an isolated PostgreSQL 17 instance in Sydney — usually ready in a minute or two.
- Free — kick the tyres. One per workspace, auto-pauses when idle, no card needed.
- Hobby / Launch / Scale — always-on production tiers with more CPU, RAM, storage and pooled connections. Scale additionally unlocks masked clones (step 9).
Every database — on every tier — gets daily backups, point-in-time recovery, TLS, a connection pooler, and the instant REST API. See the full plan table for specs. Prices are in AUD and exclude GST (10% is added at checkout).
Connect to it
Once the database shows Active, click Connection on its row to reveal two connection strings. Use the right one for the job:
- Pooled (
DATABASE_URL) — routed through PgBouncer in transaction mode. Use this for your app at runtime; it handles many short-lived connections efficiently. - Direct (
DIRECT_URL) — a session-mode connection straight to Postgres. Use this for migrations,psql, and anything needing session features (advisory locks,LISTEN/NOTIFY, prepared statements across calls).
Both are TLS-encrypted — always keep sslmode=require. Connect with anything that speaks Postgres:
# Connect with psql (use the Direct string for interactive work) psql "postgresql://app:YOUR_PASSWORD@ab12cd34.db.wattledb.com.au:31234/app?sslmode=require" # You're in: app=> select version();
import { Pool } from 'pg'; // Pooled connection string from the console → your app's DATABASE_URL const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const { rows } = await pool.query('select now() as ts'); console.log(rows[0].ts);
sslmode=verify-full), download the database's CA certificate from the console and point your client at it. sslmode=require already encrypts the connection.Create tables & run SQL
Click Open on a database to enter Database Studio. The SQL editor tab runs any SQL against your database — schema changes, queries, seed data.
create table employee ( id bigint generated always as identity primary key, name text, email text unique ); insert into employee (name, email) values ('Ada Lovelace', 'ada@example.com');
Prefer your own tooling? Point any migration tool (Prisma, Drizzle, Flyway, psql -f) at the Direct connection string and manage schema from code. The Studio and your migrations see the same database.
Browse your data
The Tables tab is a full data browser. Pick a table from the searchable list on the left — even with hundreds of tables — and the right pane shows its rows. Toggle between Data and Schema, adjust how many rows to load, or hit Query to drop that table straight into the SQL editor.
It's read-and-explore for quick checks; reach for the SQL editor when you want to change data.
Turn on the instant REST API
The REST API tab turns every table into a REST endpoint — no backend code. It's powered by PostgREST and served from <id>.api.wattledb.com.au. Click Enable REST API, and each table becomes an endpoint supporting GET · POST · PATCH · DELETE with filtering, ordering and pagination via query params.
Authentication & security
- Requests with no token use the
anonrole. - A JWT with
role=authenticatedunlocks the rows your policies allow. Mint a test token from the tab. - Click Secure (RLS) on a table to require a token and enforce Row-Level Security — the same policies apply to the REST API and to direct SQL, because it's one database.
# Read (filter, sort, paginate with query params) curl "https://ab12cd34.api.wattledb.com.au/employee?select=*&order=id.desc&limit=10" \ -H "Authorization: Bearer <TOKEN>" # Insert curl -X POST "https://ab12cd34.api.wattledb.com.au/employee" \ -H "Authorization: Bearer <TOKEN>" \ -H 'Content-Type: application/json' \ -d '{"name":"Grace Hopper","email":"grace@example.com"}'
@supabase/postgrest-js (the query builder inside supabase-js) at your WattleDB REST URL with a bearer token: postgrest.from('employee').select('*'). The full supabase-js client's .auth/.storage/.realtime are separate.Who can read your data — and how to check
The REST API tab tells you the state of every table. Two badges, two different problems:
| Badge | What it means | What to do |
|---|---|---|
| ⚠ public · no token needed | Anyone who knows your API hostname can read this table. No token, no account, nothing. | Use Make private in the warning on that tab. If it isn't offered, the access came from your own database — see below. |
| ⚠ no RLS · all rows | A token is required — but any token for this database reads and writes every row. Fine for a single trusted backend; wrong if you issue a token per end-user. | Press Secure (RLS), then replace the default policy (see below). |
| 🔒 RLS on | Row-Level Security is enforcing. Callers see the rows your policies allow. | Check the policy is the one you meant. |
You can always confirm it yourself from a terminal, with no credentials at all:
# 401 = this table needs a token. 200 = anyone can read it. # One table at a time — the REST API tab is the full picture. curl -s -o /dev/null -w '%{http_code}\n' \ "https://ab12cd34.api.wattledb.com.au/employee"
⚠ public · no token needed, that is what you are looking at. Make private withdraws it in one click. Anything of yours that reads the database without a token will stop working, so check first.Make private doesn't cover everything
Make private withdraws the grants we made. It deliberately leaves alone anything you granted from inside your own database, because removing it would break something you built on purpose. The tab names whatever remains and which of these it is; the fixes run in the SQL editor:
ALTER DEFAULT PRIVILEGES needs FOR ROLE. A default privilege belongs to the role that created it, and can only be removed by naming that role — leave FOR ROLE out and the statement succeeds while changing nothing. The REST API tab tells you which role to name.| If you granted… | Undo it with |
|---|---|
Read access to everyone — GRANT SELECT ON t TO PUBLIC |
REVOKE SELECT ON public.t FROM PUBLIC; |
Read access to future tables — ALTER DEFAULT PRIVILEGES … TO PUBLIC |
ALTER DEFAULT PRIVILEGES FOR ROLE <role> IN SCHEMA public REVOKE SELECT ON TABLES FROM PUBLIC; |
A single column — GRANT SELECT (email) ON t TO anon |
REVOKE SELECT (email) ON public.t FROM anon; |
A role that anon belongs to |
REVOKE reporting_ro FROM anon; |
A SECURITY DEFINER function (callable at /rpc/, and it runs as its owner — so making tables private does not stop it) |
REVOKE EXECUTE ON FUNCTION public.fn() FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.fn() TO authenticated; |
permission denied in the SQL editor? The editor runs as your database's application user, and you can only change grants — or drop policies — on objects you own. Tables created through the console before 25 July 2026 are owned by the system user instead. Make private is unaffected: it runs with full privileges on our side, which is exactly why it exists. But the hand-written REVOKEs above, and DROP POLICY, will fail on those tables — email support@wattledb.com.au with a screenshot of the REST API tab and we will do it for you.Row-Level Security: replace the default policy, don't add to it
Secure (RLS) turns RLS on and creates one policy, authenticated_all, which allows everything — so nothing breaks the moment you press it. It is a starting point, not the destination. Write your own policy and drop authenticated_all:
-- each end-user sees only their own rows (sub = the JWT's subject claim). -- the claim is text, so cast it to match your column's type (::uuid, ::int, ...) CREATE POLICY own_rows ON public.employee FOR ALL TO authenticated USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'sub')::uuid); -- essential: policies are OR'd together, so leaving the allow-all one -- in place means your new policy changes nothing at all DROP POLICY authenticated_all ON public.employee;
Roles & credentials — be your own DB admin
Every database ships with one credential, app, and it owns the database — it can read and write every table, and change the schema. That's the right credential for your migrations. It is the wrong one to give a reporting tool, a contractor, or a script you're debugging.
The Roles tab creates additional logins, each with its own password and its own level of access. Pick a name, pick an access level, and you get a connection string back.
| Access level | Can do | Use it for |
|---|---|---|
| Read-only | SELECT on every table, including ones you create later | Reporting, BI tools, analysts, read-only dashboards |
| Read & write | SELECT · INSERT · UPDATE · DELETE, no schema changes | Your application at runtime |
| Owner | Everything app can do, including CREATE/ALTER/DROP | Migration jobs only |
| Connect only | Log in, nothing else | When you want to write the GRANTs yourself |
Or do it in SQL
The app role holds CREATEROLE on your database, so you can manage roles from the SQL editor or psql as well. Roles you make this way appear in the Roles tab alongside the rest.
create role reporting_ro login password '…'; grant connect on database app to reporting_ro; grant usage on schema public to reporting_ro; grant select on all tables in schema public to reporting_ro; -- and for tables you create later. FOR ROLE app matters: -- a default privilege only applies to objects created by the role that set it. alter default privileges for role app in schema public grant select on tables to reporting_ro;
What you can't do, by design: create a superuser, grant BYPASSRLS, or alter WattleDB's own roles (anon, authenticated, authenticator). PostgreSQL enforces those limits itself — a role you create can never hold more than the role that created it.
If a connection string leaks
Committed to a repository, pasted into a ticket, or held by someone who's left? On the Roles tab, Rotate app password replaces the built-in credential and hands you the new connection strings. Anything still using the old one stops being able to reconnect — which is the point, so deploy the new value first if you can. Extra roles you created are rotated or deleted individually, without touching app.
Deleting a role never takes data with it: anything it created is handed to app first.
One thing to know: app can change its own password with alter role app password … in SQL, but the console won't know you did — the Connection panel would keep showing the old string. Use the button and both stay in step.
Store files with object storage
The Storage tab gives each database its own private, S3-compatible bucket on Australian-owned infrastructure. Grab the endpoint, region (au-syd-1), bucket name and access keys, and use any S3 client — path-style addressing.
aws --endpoint-url https://storage.wattledb.com.au --region au-syd-1 \ s3 cp ./report.pdf s3://tenant-ab12cd34/report.pdf
import { S3Client } from '@aws-sdk/client-s3'; const s3 = new S3Client({ endpoint: "https://storage.wattledb.com.au", region: "au-syd-1", forcePathStyle: true, // required credentials: { accessKeyId: "…", secretAccessKey: "…" }, });
Each key can access only that database's bucket, and files never leave Australian-owned infrastructure.
Backups & point-in-time restore
Every database is backed up daily (02:00 AEST) with continuous WAL archiving, stored cross-state in Melbourne for disaster recovery. The Backups page lists every backup and lets you trigger an extra one anytime.
Restore to any moment in the last 7 days
Restore creates a new database recovered to the point you choose — your current one is never touched. Pick Latest or a specific backup, choose a plan (at least the source's storage), confirm, and a fresh database provisions. Verify it, repoint your app to its connection string, then delete whichever you no longer need. It's billed as a normal database — no restore fee.
Masked clones for safe test data
Need production-shaped data in a staging environment without exposing real PII? A masked clone copies a Scale database into a new one and anonymises personal fields on the copy — emails, names, phone numbers, addresses and dates of birth are replaced with realistic but fake values. Your source is never modified.
From a Scale database's Studio, choose Masked clone, pick a plan and an expiry (minimum 7 days, AEST), and confirm. It's billed pro-rata for the days you keep it and auto-expires — extend anytime if you need it longer.
Scale up (or down) in place
Change plan resizes a database without a data migration — same database, same connection string. CPU, RAM and pooled connections apply after a brief restart; storage grows immediately.
Watch what's happening — Logs
The Logs tab is a read-only viewer with two streams:
- REST API — every request to your auto-generated API (method, path, status).
- Database — Postgres connections, disconnections, errors and notices.
Filter in place, download the current window, and know it's collected into a durable store in Sydney (retained 30 days) that never leaves Australian-owned infrastructure.
Lock down your account
Under Settings: enable two-factor authentication (scan the QR with any authenticator app and save your recovery codes), change your password (which signs out other devices), sign out everywhere if you lose a device, and manage your workspace. Changing your password rotates your session automatically.
Reference — Plans
All tiers include daily backups, PITR, TLS, connection pooling and the REST API. Prices in AUD, ex GST.
| Plan | Price / mo | vCPU | RAM | Storage | Pooled conns | Notes |
|---|---|---|---|---|---|---|
| Free | A$0 | 0.25 | 256 Mi | 512 Mi | 5 | One per workspace · auto-pauses when idle |
| Hobby | A$9 | 0.5 | 512 Mi | 1 Gi | 10 | Cheapest always-on |
| Launch | A$29 | 1.0 | 1 Gi | 4 Gi | 20 | Production default |
| Scale | A$99 | 2.0 | 2 Gi | 10 Gi | 60 | Unlocks masked clones |
Reference — Endpoints
| What | Address | Use for |
|---|---|---|
| Console | console.wattledb.com.au | Everything — create, manage, billing |
| Postgres (pooled) | <id>.db.wattledb.com.au | DATABASE_URL · app runtime |
| Postgres (direct) | <id>.db.wattledb.com.au | DIRECT_URL · migrations, psql |
| REST API | <id>.api.wattledb.com.au | Auto-generated REST (PostgREST) |
| Object storage | storage.wattledb.com.au | S3-compatible · bucket tenant-<id> |
<id> is your database's identifier (e.g. ab12cd34), shown on its row in the console. Ports and passwords come from the Connection panel.
Reference — FAQ
Where does my data live?
Primary compute and storage are in Sydney (NEXTDC S1); backups are replicated cross-state to Melbourne (NEXTDC M2). Everything runs on Australian-owned infrastructure, with no US company anywhere in the stack.
Is it real PostgreSQL?
Yes — standard PostgreSQL 17. Anything that speaks Postgres works: your ORM, migration tools, extensions, psql, dashboards.
What happens if my Free database goes idle?
It hibernates to save resources (the data is kept). Open the SQL editor or connect and it resumes automatically.
How am I billed?
Paid plans are charged in advance at sign-up, then monthly on the anniversary. One-off items (like masked clones) are charged upfront. GST is added at checkout, and you get a tax invoice for every payment.
Can I cancel or delete anytime?
Yes. Cancel a subscription and service runs to the end of the paid month; delete a database and it goes immediately. Within 7 days of paying for the month, both refund that payment in full. Consistent with the Australian Consumer Law.