Quickstart
Install better-supabase, generate your schema and run your first typed query.
Install
pnpm add better-supabase @supabase/supabase-js
pnpm add -D pg @supabase/postgrest-typegen@0.4.0The better-supabase command ships in the better-supabase package. pg and
@supabase/postgrest-typegen are optional peers that only the CLI loads: pg
reads your database schema, and postgrest-typegen writes database.types.ts.
Keep postgrest-typegen at the version above, which the CLI's output is tested
against; gen prints a notice for any other release, and without it, gen,
introspect and doctor stop with the install command. Every other peer is
optional too; Peers lists which subpath needs
which one. TypeScript 6 and 7 are
supported, with nodenext or
bundler module resolution. On runtimes without a native Temporal (Node 24,
Safari, Hermes), install temporal-polyfill and pass its namespace to
defineSupabase(schema, { temporal: Temporal }), or import
temporal-polyfill/global once at startup; see
Temporal.
Configure
Run pnpm better-supabase init to write the config, src/lib/supabase/index.ts,
and glue for the frameworks it finds (see init). In a
terminal it asks for the row casing and the integrations; --yes takes the
defaults. Before installing anything, npx better-supabase init does
the same and prints the install command for your package manager. Or create
better-supabase.config.ts in your project root yourself:
import { defineConfig, valibot } from "better-supabase/config";
export default defineConfig({
casing: "camel",
output: "src/lib/supabase/generated.ts",
plugins: { timestamps: true, softDelete: true },
generators: [valibot()],
});Every option has a default, so an empty config works too. A JSON config
(better-supabase.config.json) is also supported and validated by
schemas/config-v1.json.
Generate
Start your local stack and generate:
supabase start
pnpm better-supabase env # URL and keys into .env.local
pnpm better-supabase genThis writes database.types.ts (from supabase gen types) and
generated.ts with models, relation metadata and the schema object. Run
better-supabase gen --check in CI to fail on drift.
Query
import { createClient } from "@supabase/supabase-js";
import { defineSupabase } from "better-supabase";
import { schema } from "./lib/supabase/generated.ts";
export const betterSupabase = defineSupabase(schema);
const db = betterSupabase.connect(createClient(url, publishableKey));
const customers = await db.customers
.findMany({
select: ["id", "name"],
where: { status: "active", notes: { some: { kind: "call" } } },
include: { organization: { select: ["name"] } },
orderBy: { name: "asc" },
limit: 20,
})
.orThrow();
// { id: string; name: string; organization: { name: string } }[]defineSupabase holds no secrets and no connection. Call connect() per
request with the client for the current user, so RLS always applies.
Next steps
Last updated on