Skip to content

Quickstart

This assumes you’ve completed Installation and have a stack healthy at http://localhost:39001. We’ll create a project, a table, an RLS policy, and read/write data — first with curl, then with the SDK.

Set a management token. The bootstrap admin credential for /api/v1/* is ANVILBASE_ADMIN_TOKEN — it is the only token the management API accepts as a root admin (CONTROL_PLANE_SECRET is a separate secret that only signs JWTs, not a management token). anvilbase start generates ANVILBASE_ADMIN_TOKEN for local stacks (and anvilbase status --local prints the matching export ANVILBASE_TOKEN=<…> tip); for a raw docker compose up deployment you set it yourself — add a line like ANVILBASE_ADMIN_TOKEN=$(openssl rand -hex 32) to .env, re-run docker compose up -d to apply it, then export the same value:

Terminal window
export ANVILBASE_TOKEN="$(grep '^ANVILBASE_ADMIN_TOKEN=' .env | cut -d= -f2)"
# (Better: create a Personal Access Token — see the Management API reference.)

1. Create a project

Terminal window
curl -s -X POST http://localhost:39001/api/v1/projects \
-H "Authorization: Bearer $ANVILBASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Quickstart"}' | tee project.json | jq .

Capture the values you’ll reuse (the service_role_key and jwt_secret are shown only once):

Terminal window
export PROJECT_ID=$(jq -r .id project.json)
export ANON_KEY=$(jq -r .anon_key project.json)
export SERVICE_KEY=$(jq -r .service_role_key project.json)
echo "Project: $PROJECT_ID"

2. Create a table

Tables are created on the management plane (RLS is on by default):

Terminal window
curl -s -X POST http://localhost:39001/api/v1/projects/$PROJECT_ID/schema/tables \
-H "Authorization: Bearer $ANVILBASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "todos",
"enable_rls": true,
"columns": [
{"name":"id","type":"uuid","primary_key":true,"default":"gen_random_uuid()"},
{"name":"user_id","type":"uuid","nullable":false},
{"name":"task","type":"text","nullable":false},
{"name":"done","type":"boolean","default":"false"},
{"name":"created_at","type":"timestamptz","default":"now()"}
]
}' | jq .

3. Add an RLS policy

Right now the table has RLS enabled but no policies, so nothing is readable (fail-closed by design). Add a policy that lets authenticated users see and manage their own rows:

Terminal window
curl -s -X POST \
http://localhost:39001/api/v1/projects/$PROJECT_ID/rls/tables/todos/policies \
-H "Authorization: Bearer $ANVILBASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "owner_all",
"command": "ALL",
"using_expression": "user_id = current_setting('\''app.current_user_id'\'')::uuid",
"with_check_expression": "user_id = current_setting('\''app.current_user_id'\'')::uuid",
"roles": ["authenticated"]
}' | jq .

You can verify a policy before trusting it with the RLS test endpoint, which runs a real query inside a transaction and rolls back:

Terminal window
curl -s -X POST http://localhost:39001/api/v1/projects/$PROJECT_ID/rls/test \
-H "Authorization: Bearer $ANVILBASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"table":"todos","role":"authenticated","user_id":"00000000-0000-0000-0000-000000000001"}' | jq .

See Row Level Security for templates.

4. Write and read data

For local exploration we’ll use the service_role key (it bypasses RLS, so you don’t need a signed-in user yet). In real client code you’d use the anon key plus a user JWT.

Terminal window
# Insert a row (service_role bypasses RLS)
curl -s -X POST "http://localhost:39001/v1/rest/$PROJECT_ID/todos" \
-H "apikey: $SERVICE_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"user_id":"00000000-0000-0000-0000-000000000001","task":"Try AnvilBase"}' | jq .
# Read rows, filtered and ordered (PostgREST syntax)
curl -s "http://localhost:39001/v1/rest/$PROJECT_ID/todos?done=eq.false&order=created_at.desc" \
-H "apikey: $SERVICE_KEY" | jq .

That’s the whole loop: create table → policy → write → read. The query syntax (done=eq.false, order=, select=, limit=…) is documented in REST Query Syntax.

5. Same thing from the SDK

AnvilBase’s data plane is @supabase/supabase-js-compatible. The SDK base URL is your host plus /v1/<project_id>:

import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'http://localhost:39001/v1/' + process.env.PROJECT_ID,
process.env.ANON_KEY!, // anon key — safe in clients
)
// Insert
const { data, error } = await supabase
.from('todos')
.insert({ user_id: userId, task: 'Try AnvilBase' })
.select()
// Read
const { data: todos } = await supabase
.from('todos')
.select('*')
.eq('done', false)
.order('created_at', { ascending: false })

The full multi-language version (Python, Dart, raw fetch) is in Connect Your App.

6. Explore in the console

Open http://localhost:39004 and you’ll find the same project: a Table Editor, SQL Editor, RLS policy builder, Storage browser, Functions editor, and more. See the console tour in Self-Hosting → Overview.

Next steps

  • Your First Project — keys, endpoints, and the lifecycle in depth.
  • Connect Your App — wire a real frontend.
  • Auth — add real users so the anon key + RLS does the work instead of service_role.
  • Realtime — subscribe to live changes on todos.