The Create core
How better-supabase/create holds the design model, previews and Supabase deploy client that /create and other apps share.
better-supabase/create is the headless code behind
Create. It runs on every runtime,
including the browser, and has no dependency beyond the Supabase packages.
Designs stay in the URL or in your own storage, as ADR 0040 requires.
import { previewFiles, diffDesigns } from "better-supabase/create";
import { namedPreset } from "better-supabase/preset";
const base = { preset: namedPreset("saas") };
const files = previewFiles(base);
console.log(files.map((file) => file.path));diffDesigns(base, next) classifies each change as safe or breaking, so an
app can block a breaking change before it deploys.
Supabase deploy
better-supabase/create/supabase signs a user in with Supabase OAuth and
PKCE, seals the session in a cookie value, and wraps the Management API.
import { managementClient } from "better-supabase/create/supabase";
const client = managementClient(accessToken, fetch);
const projects = await client.projects();Pass your own fetch to trace or stub the calls in tests.
Migrate between versions
migrationBetween(code, base, next) writes the migration that takes a project
deployed from one design to the next. It covers added tables and added
columns, which the app-table SQL applies idempotently.
import { migrationBetween } from "better-supabase/create";
const plan = migrationBetween(code, deployed, edited);
if (plan.ok) {
await client.applyMigration(ref, "better_supabase_design", plan.sql);
} else {
console.log(
plan.reason,
plan.changes.map((change) => change.steps),
);
}A breaking refusal lists the expand and contract steps for each change. An
unsupported refusal covers any other change, such as a new default or a
change outside appTables, so a deploy never guesses.
Validate in Postgres
better-supabase/create/lite applies the generated files to an in-memory
PGlite database through the optional @supabase/lite peer, and reports what
Postgres rejects before anything reaches a project.
import { previewFiles } from "better-supabase/create";
import { validateDesign } from "better-supabase/create/lite";
import { encodePreset, namedPreset } from "better-supabase/preset";
const files = previewFiles(encodePreset(namedPreset("saas")));
const { ok, issues, skipped } = await validateDesign(files);PGlite starts without a Supabase project's roles and auth schema, so the
function creates a minimal copy first. A module that needs an extension PGlite
doesn't ship (pgmq for jobs, btree_gist for time tracking) lands in
skipped instead of failing. pgTAP is not available through
@supabase/lite, so database tests still run with supabase test db.
Last updated on