Skip to content

Connect Your App

AnvilBase’s data plane is @supabase/supabase-js-compatible, so the official Supabase clients in several languages work against it. This page shows how to connect from TypeScript, Python, Dart/Flutter, and plain HTTP.

You need two things from Your First Project:

  • the project URL: http://<host>/v1/<project_id> (e.g. http://localhost:39001/v1/3f2b…)
  • the anon key (anvilbase_anon_<slug>_…) for client code, or the service_role key for trusted server code.

Key choice. Use the anon key in anything a user can see (browser, mobile, public CLI) — RLS protects your data. Use the service_role key only in server-side code; it bypasses RLS. See API Keys & Scopes.

TypeScript / JavaScript (@supabase/supabase-js)

Terminal window
npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js'
const ANVILBASE_URL = 'http://localhost:39001/v1/<project_id>'
const ANON_KEY = 'anvilbase_anon_my-app_…'
export const db = createClient(ANVILBASE_URL, ANON_KEY)
// READ — filter, order, paginate
const { data: todos, error } = await db
.from('todos')
.select('id, task, done, created_at')
.eq('done', false)
.order('created_at', { ascending: false })
.range(0, 19) // first 20 rows
// INSERT
await db.from('todos').insert({ task: 'Ship it', user_id: userId })
// UPDATE
await db.from('todos').update({ done: true }).eq('id', todoId)
// DELETE
await db.from('todos').delete().eq('id', todoId)
// UPSERT
await db.from('todos').upsert({ id: todoId, task: 'Revised' })
// CALL a Postgres function (RPC)
const { data } = await db.rpc('search_todos', { q: 'ship' })

Using a signed-in user’s JWT

For server-side or non-SDK callers that already hold a project JWT (e.g. minted via the raw token endpoint), pass it so requests run as authenticated and RLS scopes to that user. (In the browser, prefer db.auth.signInWithPassword() below — the SDK attaches the header for you.)

const db = createClient(ANVILBASE_URL, ANON_KEY, {
global: { headers: { Authorization: `Bearer ${userJwt}` } },
})

Realtime

const channel = db
.channel('todos-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'todos' },
(payload) => console.log('change:', payload)
)
.subscribe()

See Realtime → Postgres Changes.

Storage

// Upload
await db.storage.from('avatars').upload(`${userId}/photo.jpg`, file)
// Public/Signed URL
const { data } = await db.storage.from('avatars')
.createSignedUrl(`${userId}/photo.jpg`, 3600)

See Storage → Working with Objects.

Python (supabase-py)

Terminal window
pip install supabase
from supabase import create_client, Client
ANVILBASE_URL = "http://localhost:39001/v1/<project_id>"
ANON_KEY = "anvilbase_anon_my-app_…"
db: Client = create_client(ANVILBASE_URL, ANON_KEY)
# READ
res = (
db.table("todos")
.select("id, task, done")
.eq("done", False)
.order("created_at", desc=True)
.limit(20)
.execute()
)
todos = res.data
# INSERT
db.table("todos").insert({"task": "Ship it", "user_id": user_id}).execute()
# UPDATE
db.table("todos").update({"done": True}).eq("id", todo_id).execute()
# DELETE
db.table("todos").delete().eq("id", todo_id).execute()
# RPC
db.rpc("search_todos", {"q": "ship"}).execute()

For trusted server jobs, pass the service_role key instead of the anon key to bypass RLS.

Dart / Flutter (supabase_flutter)

pubspec.yaml
dependencies:
supabase_flutter: ^2.0.0
import 'package:supabase_flutter/supabase_flutter.dart';
await Supabase.initialize(
url: 'http://10.0.2.2:39001/v1/<project_id>', // 10.0.2.2 = host from Android emulator
anonKey: 'anvilbase_anon_my-app_…',
);
final db = Supabase.instance.client;
// READ
final todos = await db
.from('todos')
.select()
.eq('done', false)
.order('created_at', ascending: false)
.limit(20);
// INSERT
await db.from('todos').insert({'task': 'Ship it', 'user_id': userId});
// UPDATE / DELETE
await db.from('todos').update({'done': true}).eq('id', todoId);
await db.from('todos').delete().eq('id', todoId);
// Realtime
db.channel('todos').onPostgresChanges(
event: PostgresChangeEvent.all,
schema: 'public',
table: 'todos',
callback: (payload) => debugPrint('$payload'),
).subscribe();

Raw HTTP (any language)

The data plane is plain REST. Two headers matter: apikey (your project key) and, for a signed-in user, Authorization: Bearer <jwt>.

Terminal window
BASE="http://localhost:39001/v1/rest/<project_id>"
# READ
curl -s "$BASE/todos?done=eq.false&order=created_at.desc&limit=20" \
-H "apikey: $ANON_KEY"
# INSERT (return the inserted row)
curl -s -X POST "$BASE/todos" \
-H "apikey: $ANON_KEY" -H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"task":"Ship it","user_id":"…"}'
# UPDATE
curl -s -X PATCH "$BASE/todos?id=eq.$ID" \
-H "apikey: $SERVICE_KEY" -H "Content-Type: application/json" \
-d '{"done":true}'
# DELETE
curl -s -X DELETE "$BASE/todos?id=eq.$ID" -H "apikey: $SERVICE_KEY"
# RPC
curl -s -X POST "$BASE/rpc/search_todos" \
-H "apikey: $ANON_KEY" -H "Content-Type: application/json" \
-d '{"q":"ship"}'

Go, Rust, Java, etc. work the same way — point any HTTP client at /v1/rest/....

Auth is drop-in too

The SDK’s auth module works unmodified — AnvilBase serves a GoTrue-compatible facade at the same paths supabase-js calls, so you use db.auth.* on the same createClient instance:

// same db from "Connecting" above — no separate client, no manual fetch
await db.auth.signUp({ email, password })
const { data } = await db.auth.signInWithPassword({ email, password })
// db is now authenticated; the SDK sends the Bearer token on every request.
await db.auth.signInWithOtp({ email }) // magic-link / email OTP
await db.auth.getUser()
await db.auth.signOut()

You do not need to fetch a JWT by hand or inject Authorization yourself for SDK flows — the “Using a signed-in user’s JWT” block above is only for server-side / non-SDK callers that already hold a token.

Auth compatibility notes. The standard flows are drop-in, and the service-role supabase.auth.admin.* user-management surface is served too (it requires the project service_role key). One edge to know: a user created via OTP / magic-link sign-in does not capture signup-time options.data metadata at first verify — password and anonymous signup persist it immediately; for OTP/magic-link, call updateUser({ data }) after the first sign-in. See Auth → Overview.

Full endpoint list and the compatibility matrix: Auth → Overview and Tools → SDK Compatibility.

The reference app

examples/todo-app/ in the repo is a complete CRUD + realtime + storage demo wired against the SDK — the fastest way to see a working end-to-end integration.

Next: Database → Overview.