WattleDB DEVELOPER GUIDE

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.

PostgreSQL 17 Sydney · au-syd-1 Auto REST API Daily backups + PITR No US parent company
01

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.

Turn on two-factor authentication under Settings once you're in (step 12) — it takes 30 seconds and protects the keys to your data.
02

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

Paid tiers need no payment details up front. Create the database and it runs straight away; we email a GST tax invoice, payable within 7 days by bank transfer or PayID. Every invoice is under Billing in the console.
03

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:

terminal
# 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();
app.ts — node-postgres
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);
For full certificate verification (sslmode=verify-full), download the database's CA certificate from the console and point your client at it. sslmode=require already encrypts the connection.
04

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.

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

05

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.

06

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 anon role.
  • A JWT with role=authenticated unlocks 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.
terminal
# 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"}'
The API speaks the PostgREST protocol, so PostgREST clients work against it — point @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:

BadgeWhat it meansWhat 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:

terminal
# 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"
Databases that turned the REST API on before July 2026 carry a legacy default: enabling the API granted read access to the anonymous role across every table. We stopped doing that on 20 July 2026, but it was never withdrawn from databases that already had the API on. If you see ⚠ 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;
Getting 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:

SQL editor
-- 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;
07

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 levelCan doUse it for
Read-onlySELECT on every table, including ones you create laterReporting, BI tools, analysts, read-only dashboards
Read & writeSELECT · INSERT · UPDATE · DELETE, no schema changesYour application at runtime
OwnerEverything app can do, including CREATE/ALTER/DROPMigration jobs only
Connect onlyLog in, nothing elseWhen you want to write the GRANTs yourself
Passwords are generated and shown once. Nothing stores them — not us, not the console. If one is lost, click New password rather than going looking for it.

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.

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

08

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.

terminal — aws cli
aws --endpoint-url https://storage.wattledb.com.au --region au-syd-1 \
  s3 cp ./report.pdf s3://tenant-ab12cd34/report.pdf
upload.ts — aws-sdk v3
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.

09

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.

A restore needs at least one completed backup to recover from. Brand-new database? Hit Back up now and wait for it to complete before restoring.
10

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.

Masked clones are one-off and non-refundable on early deletion (you can always extend). Great for demos, load tests and onboarding a new engineer safely.
11

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.

Storage can only grow — disks don't shrink. Size for where you're heading, not just today.
12

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.

13

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.

14

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.

15

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.


R

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.

PlanPrice / movCPURAMStoragePooled connsNotes
FreeA$00.25256 Mi512 Mi5One per workspace · auto-pauses when idle
HobbyA$90.5512 Mi1 Gi10Cheapest always-on
LaunchA$291.01 Gi4 Gi20Production default
ScaleA$992.02 Gi10 Gi60Unlocks masked clones
R

Reference — Endpoints

WhatAddressUse for
Consoleconsole.wattledb.com.auEverything — create, manage, billing
Postgres (pooled)<id>.db.wattledb.com.auDATABASE_URL · app runtime
Postgres (direct)<id>.db.wattledb.com.auDIRECT_URL · migrations, psql
REST API<id>.api.wattledb.com.auAuto-generated REST (PostgREST)
Object storagestorage.wattledb.com.auS3-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.

R

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.