Ship on Australian-owned Postgres.
WattleDB gives you a fully-managed PostgreSQL database — plus an instant REST API, object storage, and database backups with 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 live. Create a workspace and start on the free sandbox tier — no credit card required. Already have an account? 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 at least daily backups (hourly on Scale), 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 on the invoice).
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.
Know the scope before you put anything here. Buckets are served from Sydney. Deleted or overwritten files stay recoverable for 30 days, and a backup copy of each bucket is held cross-state in Melbourne, refreshed every six hours. That copy holds current versions only, is a restore path rather than a second endpoint, and object storage is still not covered by the SLA. Keep your own master copy of anything you cannot re-create, and put records you must retain in PostgreSQL, which is backed up cross-state with point-in-time recovery.
Never put the bucket key in a browser or mobile app — it can read, write and delete every file in the bucket. For direct browser uploads, mint a short-lived presigned URL on your server with getSignedUrl and hand that to the client instead. Presigning needs no extra permissions.
Recovering a file. Deleted or overwritten objects stay recoverable for 30 days: aws s3api list-object-versions to find the version, aws s3api get-object --version-id to fetch it. Deleting a specific version erases it immediately and permanently.
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 on a schedule (daily, early morning UTC, hourly on Scale) with continuous write-ahead-log archiving, stored cross-state in Melbourne for disaster recovery. The Backups page lists every backup and lets you trigger an extra one anytime.
Restore from any listed backup, or to the latest moment, within your plan's recovery window
Recovery windows are 7 days on Free, Hobby and Launch, and 30 days on Scale. 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 replaces common contact fields on the copy — emails, names, phone numbers, addresses and dates of birth — with realistic but fake values. Columns are matched by name, so identifiers such as Medicare, TFN or member numbers, free-text notes and PII in numeric columns are not masked automatically: a masked clone is a large reduction in the personal information sitting in staging, not a guarantee that the copy is de-identified. Check it against your own schema. 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.
See what changed — History
History, in the console sidebar under Account, is the record of everything that has happened in your workspace and to your own account. Open it at console.wattledb.com.au → your workspace → History, between Billing and Settings.
It answers the questions that come up after the fact: when a database was created or deleted, when a plan changed, when an invoice was paid, when billing details were edited, and whether a sign-in to your account was you.
Each entry says who did it, which is the part worth reading closely:
- You — an action you took while signed in.
- Support — someone at WattleDB acting on your account. If you did not ask for it, that is a question worth sending to support@wattledb.com.au.
- Platform — the platform acting on its own, such as destroying a bucket and its backups when a database is deleted. These are not actions anyone took by hand.
Sign-in events are yours alone. They are shown to the account they belong to and to nobody else, including a workspace owner looking at their own History. A failed sign-in is highlighted, because a run of them is the thing worth noticing.
Events are listed newest first, twenty to a page. History reaches back as far as our retention allows: security events such as sign-ins are kept for twelve months, database lifecycle events for twelve months, billing and financial records for seven years, and routine operational entries for six months. We record no IP address and no location against these events, in keeping with our Privacy Policy.
Hearing from us — notifications
The bell in the top bar of the console carries platform notices: planned maintenance, upcoming deployments, and anything else worth knowing in advance. A dot appears when there is something you have not read.
Separately, if the console itself cannot reach our API — usually a deployment, normally under a minute — a banner appears saying so. It is worth reading what it says, because the answer is almost always that nothing of yours is affected: your database connections, your REST API and your object storage do not go through the console and keep running while it is unavailable. What pauses is creating and changing databases, billing, and usage figures.
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 at least daily backups (hourly on Scale), 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, and no one outside WattleDB and our Australian hosting and data-centre providers has access to your database or its backups.
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 invoiced in advance when you create the database, then monthly on the anniversary. One-off items (like masked clones) are invoiced upfront. Each invoice is a GST tax invoice payable within 7 days by bank transfer or PayID — we hold no card or bank details and charge nothing automatically.
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. If you do either within 7 days of paying, we refund that month's payment — the one you just made, not earlier months you have already used. Consistent with the Australian Consumer Law.