Stability
What counts as public API, how it is kept stable and how deprecations work.
Public API
The public API is every export of every subpath in package.json, the CLI
commands and flags, the config file, the JSON Schemas in schemas/, the
doctor codes and the generated module's shape.
Two tests guard it:
- Export snapshot.
api/exports.jsonlists the value and type exports of every subpath. A change shows up in review as a diff of that file. - Type tests for every extension interface check that the first-party implementations still satisfy it.
Versioning
better-supabase follows semver. Until 1.0, breaking changes ship in minor versions and are called out in the changelog.
Plugins and extension interfaces carry apiVersion: 1. A breaking change to
their contract needs a new apiVersion; use() rejects plugins built for a
version it doesn't support, so the failure is immediate instead of subtle.
The config is a strict object: the CLI rejects a field it doesn't know, in
the config and in an authorization provider, instead of ignoring it. A
provider built for another apiVersion gets one error naming both versions,
not a list of the fields that changed. New optional fields join an
apiVersion in a minor release; a field that changes meaning or becomes
required needs a new apiVersion.
Deprecations
- The API is marked
@deprecatedin its TSDoc, with the replacement. Editors strike it through. - It keeps working for at least one minor release (after 1.0: until the next major).
- The changelog and this site's migration notes describe the replacement.
- It is removed, and
api/exports.jsonshows the removal.
Doctor codes are never reused: a retired check keeps its code reserved.
BS101, BS102, BS104, BS105, BS201, BS202 and BS203 are retired; Supabase's
own advisors report those problems now, as BS100 and BS200.
A new check gets a new code and ships in a minor release, because it can make
a passing run report findings: BS205 to BS209, BS211 and BS212 (RLS
performance, statistics and plans) arrived that way, and so did BS213
(API roles that can write the columns granting access), BS214 (permissions the provider doesn't fully answer in Storage and Realtime
policies), BS407 (two authorization hooks), BS408 (the provider's memberships
for entitlements) and BS409 (the authorization provider and the config
disagree), BS410 (HTTP auth hooks), BS411 (the provider access model can't use
the provider, in 0.5.1, because the configurations it reports rendered
functions that call missing helpers), and
BS307 to BS314 (custom block contracts, the tenant claim, deprecated block
symbols, duplicate block triggers, SQL modules behind their version, exposed
block schemas, rate limits not wired to PostgREST and migration-only block
options), BS315 (tables without an audit trigger), BS320 (tables without
the session policy), BS322 (audit registrations of dropped tables),
BS323 (module event triggers missing from the database or the migrations)
and BS324 (a roles table shared by tenant and platform roles without a
where on both sides), BS325 (the audit log readable only as the service
role while the app lists it as a user), BS326 (deleteAccount or admin
without SUPABASE_SECRET_KEY in the env files) and BS327 (requireAal('aal2')
without MFA enroll and verify in config.toml).
SQL module objects and claims
The SQL modules' database objects are public API too: the functions, tables,
columns and triggers each module creates, the claim names its functions read
(tenant_id, tenant_role, is_platform), and the
access contract (can(), tenant_ids_with(),
is_platform()). Apps call them from policies and their own functions, so a
rename breaks the database, not the build. They follow these rules:
- Each module has a version, recorded in its file's
@bs-moduleline and inbetter_supabase.modules. A change the schema diff can't follow (a renamed column, a backfill) raises the version and ships a forward step forbetter-supabase sql upgrade. - A renamed function keeps a wrapper under the old name for at least one
minor release, with a
comment on function ... is 'deprecated: use X'. A renamed table keeps a view under the old name, with the old column names, for the same period. A renamed claim is read under both names. A renamed column is renamed in place by the upgrade step, so code that still uses the old name fails until it changes; doctor finds it first. - Doctor reports code that still uses a deprecated or removed symbol (BS309), and a module behind its version (BS311).
- A removal is listed in the changelog with the replacement.
Names you map in sql.modules.<module> (tables, columns, statuses) and claim names
you set in claims are yours: the block never renames them. Options that only
match an existing schema are accepted in adopt mode only, and doctor warns
about them (BS314); a later release can drop one after a deprecation in the
changelog.
Subpaths
Every subpath is public API with the same guarantees, including
better-supabase/plugins/rules (the rules() plugin, its rule names and
presets) and better-supabase/lint (lint rule ids and their options).
Adding a rule to a preset can make a passing app report new violations, so
it ships in a minor release with a changelog entry.
Pinned upstreams
Some output depends on Supabase projects. They're pinned so an upstream release never changes your generated code or doctor report by itself:
@supabase/postgrest-typegenis an optional peer of the CLI. The peer accepts>=0.4.0 <0.5, and the CLI is tested against 0.4.0;genprints a notice when another release is installed.database.types.tsmatchessupabase gen typesfor that version, apart from differences the CI test lists (0.4.0 addsComputedFields, which Supabase CLI 2.119 doesn't write yet). Bumping it is a changelog entry.- splinter (the advisor lints) is fetched at a pinned commit and checked against a SHA-256 hash when doctor runs locally. It's never bundled.
@supabase/configis an optional peer with a version range. Without it,config.tomlis read with a built-in parser that supports what better-supabase needs.
Last updated on