CLI
Codegen, SQL and diagnostics for better-supabase projects.
pnpm better-supabase <command> [options]| Command | What it does |
|---|---|
init | Writes the config, lib/supabase/index.ts and glue for the frameworks it finds |
add | Adds an integration: next, hono, orpc, edge, mcp, client, react |
gen | Generates types, metadata and validators from your database |
introspect | Saves a schema snapshot, for codegen without a database |
env | Writes the local stack's URL and keys to .env.local |
keys | Creates or rotates an ES256 signing key for the local stack |
seed | Renders typed fixtures to seed SQL |
spec | Writes, checks and validates the OpenAPI, AsyncAPI and Arazzo documents of a defineApi module |
scaffold api | Writes an API module, resource routes and an OpenAPI route for Hono or Next.js |
openapi emit | Writes openapi.json from your createOpenApi module |
sql | Lists, adds, syncs, upgrades and prints SQL modules |
config | Prints the resolved config, with every default filled in |
codemod | Rewrites imports and calls for renamed better-supabase APIs |
doctor | Checks RLS, indexes, drift, auth config and env files; text, JSON, SARIF or GitHub annotations |
skills | Installs the Agent Skills that ship with the package |
Every command takes --help. Commands that write files accept --check
(exit 1 on drift, with a unified diff of each stale file) or --dry-run.
In a terminal, output is colored (set NO_COLOR to turn it off), init and
add ask what they need, and database work shows a spinner on stderr.
A mistyped command gets a suggestion: Did you mean "gen"?.
Global options
| Option | Effect |
|---|---|
--cwd <dir> | Project directory. Defaults to the current one. supabase/config.toml is read from here or the nearest parent that has one (monorepos). |
--config <file> | Config file, relative to --cwd. Defaults to the nearest better-supabase.config.* in --cwd or a parent directory. |
--json | Prints one JSON document on stdout, and errors as Problem Details. Never prompts. |
--yes, -y | Never prompts; uses the flags and the defaults. |
--help, -h | Prints the usage of the CLI, or of one command. |
--version | Prints the version. |
Prompts only run in a terminal. In CI ($CI is set), and under --json or
--yes, commands use their flags and defaults instead of asking.
Exit codes and JSON output
The CLI exits with 0 on success, 1 when a command fails or finds a problem
(--check drift, doctor errors), and 2 when the command line, the config or
the environment needs a change. Every error has a code, listed on the
errors page.
With --json, stdout holds exactly one JSON document: the command's result
(gen prints { "tables", "written" }, doctor its report), or a
Problem Details document when it
fails. Progress and diagnostics go to stderr, so
better-supabase gen --check --json | jq .stale stays parseable.
Environment
| Variable | Used by |
|---|---|
DATABASE_URL | gen, introspect, doctor and seed --apply, when nothing else is set |
SUPABASE_DB_URL | The same commands, when DATABASE_URL is unset too |
SUPABASE_ACCESS_TOKEN | --project-ref and source.projectRef |
SUPABASE_API_URL | Another Management API host |
SUPABASE_BIN | The Supabase CLI env runs, instead of supabase on the PATH |
CI | Turns prompts off |
The CLI checks these once per run; a malformed URL stops it with
env_invalid. Connection strings hold the
database password, so no command takes one as an argument. Set
$DATABASE_URL, or $SUPABASE_DB_URL (the name the Supabase CLI and
better-supabase env use), or pipe one in with --db-url-stdin. When both
are set, $DATABASE_URL wins.
Programmatic use
The CLI never calls process.exit. run returns the exit code and output,
which is how the tests drive it:
import { run } from "better-supabase/cli";
const { code, stdout, stderr } = await run(["gen", "--check"], {
env: process.env,
});run reads no process globals: it sees only the env you pass (none by
default), and --cwd resolves against the cwd option. help() returns the
usage text, and help(command) the text for one command.
Add your own commands with defineCliCommand and registerCommand. Commands
are citty definitions: args declares the
options, and run gets the parsed arguments and the context (cwd,
config, io, env, json, signal). Throw a CliError to report a
coded error, and return data to give --json a document. Options listed in lists may repeat or
take commas, and list() splits them.
import {
defineCliCommand,
list,
registerCommand,
run,
} from "better-supabase/cli";
registerCommand(
"schemas",
defineCliCommand({
meta: { name: "schemas", description: "Prints the schemas codegen reads" },
args: { only: { type: "string", description: "Schemas to keep" } },
lists: ["only"],
run: async (args, { config }) => {
const only = list(args.only);
const schemas = config.schemas.filter(
(name) => only.length === 0 || only.includes(name),
);
return { code: 0, output: schemas.join("\n") };
},
}),
);
const { code } = await run(process.argv.slice(2), {
cwd: process.cwd(),
env: process.env,
});
process.exitCode = code;Commands written for 0.2, (context) => CommandResult, still register with
registerCommand(name, command, help). That overload is deprecated.
Last updated on