# Stability

> What counts as public API, how it is kept stable and how deprecations work.

Source: https://bettersupabase.com/docs/extending/stability

## Public API [#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.json` lists 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 [#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 [#deprecations]

1. The API is marked `@deprecated` in its TSDoc, with the replacement. Editors
   strike it through.
2. It keeps working for at least one minor release (after 1.0: until the next
   major).
3. The changelog and this site's migration notes describe the replacement.
4. It is removed, and `api/exports.json` shows 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 [#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](/docs/blocks/access) (`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:

1. Each module has a version, recorded in its file's `@bs-module` line and in
   `better_supabase.modules`. A change the schema diff can't follow (a
   renamed column, a backfill) raises the version and ships a forward step
   for `better-supabase sql upgrade`.
2. 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.
3. Doctor reports code that still uses a deprecated or removed symbol
   (BS309), and a module behind its version (BS311).
4. 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 [#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 [#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-typegen`** is an optional peer of the CLI. The
  peer accepts `>=0.4.0 <0.5`, and the CLI is tested against 0.4.0; `gen`
  prints a notice when another release is installed.
  `database.types.ts` matches `supabase gen types` for that version, apart
  from differences the CI test lists (0.4.0 adds `ComputedFields`, 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/config`** is an optional peer with a version range. Without
  it, `config.toml` is read with a built-in parser that supports what
  better-supabase needs.