From 0.57 to 0.58
What to change when upgrading from better-supabase 0.57 to 0.58. One naming convention for config keys, exports, SQL objects and blocks, declarative module files, CLI contract 3, and the codemods that apply most of it. The organization key moves to auth.tenant by hand.
0.58 gives every name one spelling and makes the files better-supabase writes
declarative. It is a minor release, and below 1.0 a minor version may carry
breaking changes. The renames ship without aliases, so a project that keeps an old
name fails with a message that names the new one. Most changes have a codemod;
three need you: the organization config key, scripts that pass renamed CLI
flags, and a cloud design URL that you saved before this release.
An app on an older version than 0.57 upgrades to 0.57 first, then follows this guide. Each change is also in the changelog.
| Section | Needs code |
|---|---|
| Order of work | Run the codemods, gen, upgrade and your SQL workflow |
| Config keys | Run codemod naming, then move organization by hand |
| Exports and types | Run codemod naming and codemod generators |
| Blocks | Run codemod naming, then upgrade |
| Invoice numbers | Remove options.uniqueNumber, then check for duplicates |
| Database names | Run codemod naming, upgrade, then apply the migration |
| Declarative module files | Run upgrade and commit the result |
| Your own columns and tables | Run codemod extra-columns, owned-columns and config |
| Generated client code | Run gen, then codemod table-modules |
| CLI | Change scripts, CI and cron entries |
| Removed | Check that nothing depends on a shim |
| Stays as it is | None; read the notes |
Order of work
-
Move to 0.57, run
better-supabase doctor, and commit a clean tree. -
Bump
better-supabaseto 0.58 and the peers it lists. -
Run the codemods, in this order, and commit after each one:
better-supabase codemod naming better-supabase codemod owned-columns better-supabase codemod extra-columns better-supabase codemod config better-supabase codemod generators better-supabase codemod server-imports better-supabase codemod table-modulescodemod namingcomes first because it rewrites text, so it runs when the config no longer loads. It renames the config keys this release renamed, and the codemods after it read the config:owned-columns,extra-columnsandconfigstop withconfig_invalidwhen it still does not load. They come beforegeneratorsbecause they read options and columns that this release stops accepting. -
Move the
organizationkey toauth.tenantby hand (see Config keys). -
Run
better-supabase gento rewrite the generated files, thenbetter-supabase upgradeto rewrite the module files. -
Write the migration with your SQL workflow (
supabase db schema declarative syncfor pg-delta, orbetter-supabase db diff) and read it before you apply it.codemod naminghas already written the rename migrations for columns, tables, constraints, policies and permission keys, so the diff holds only what is left. -
Run
better-supabase doctorand clear what it reports. BS260 lists a source file that still compares against an old hint code.
Config keys
Each key has one spelling. codemod naming rewrites the keys that stay under
the same parent in the file that calls defineConfig.
| Old | New |
|---|---|
subjects | recordTypes |
gen.recordTypes | gen.records |
gen.output | gen.file |
database.seed.output, database.seed.sample.output, database.realtime.policies.output | database.seed.file, database.seed.sample.file, database.realtime.policies.file |
tooling.env.output, tooling.keys.output, tooling.doctor.output, api.specs.outputs[].output | tooling.env.file, tooling.keys.file, tooling.doctor.file, api.specs.outputs[].file |
integrations (flat), and the integrations field of a preset | adapters |
organization (flat) | auth.tenant |
blocks.<name>.subjects keeps its name, because it binds a block to record
types.
The permission action keys under blocks.<name>.permissions follow ADR 0130:
view is read, viewAll is readAll, viewSensitive is readSensitive,
edit is write and admin is manage, for the blocks that had them (audit,
automations, billing, employees, impersonation, invitations,
segments, webhooks-in, webhooks-out, workflow-editor, workflows and
ai-chat). The config refuses an old key and names the new one, and codemod naming renames it.
The organization key moves to another parent, so the codemod reports it and
leaves it. Move it yourself:
export default defineConfig({
// before
// organization: { claim: "org_id", column: "organization_id" },
auth: { tenant: { claim: "org_id", column: "organization_id" } },
});The resolver reads auth.tenant.claim as the tenant claim and the rest of the
object as the tenant settings.
Two more config entries are refused instead of renamed. A
columns.<table>.<column>: null for an optional column that the view you map
lacks anyway, and a columns.<table>.tenant that repeats sql.tenantColumn.
codemod config removes both, and doctor BS379 names them.
Exports and types
api/naming.json lists every rename, and codemod naming applies the ones
that touch your code.
| Old | New |
|---|---|
buildJsonSchema, buildApiModel | toJsonSchema, defineApiModel |
createRuleSink, workItemRecurrenceSink | automationSink, taskRecurrenceSink |
defineSupabase, getBetterSupabase (Svelte) | createSupabase, betterSupabase |
SessionEntryConfig, GuardEntryConfig | SessionEntryOptions, GuardEntryOptions |
ChainProofFailure, ParseScaledFailure, RefreshFailure, StorageFailure, SvelteKitFailure, TelemetryFailure, XhrFailure | the same names ending in Error |
BSB_STACK, BSB_CLOUD_URL | BETTER_SUPABASE_STACK, BETTER_SUPABASE_CLOUD_URL |
AUTH_HOOK_SECRET, BEFORE_USER_CREATED_SECRET | BETTER_SUPABASE_AUTH_HOOK_SECRET, BETTER_SUPABASE_BEFORE_USER_CREATED_SECRET |
better-supabase/ai-sdk/workflow/react | better-supabase/ai-sdk/workflow-react |
CHAIN_SIGNER, APPEND_ONLY, FISCAL_YEAR_CLOSED (hint codes) | AUDIT_CHAIN_SIGNER, TABLE_APPEND_ONLY, PERIOD_CLOSED |
ENGAGEMENT_NO_TRANSCRIBER, EngagementInput, EngagementEventData | ACTIVITY_NO_TRANSCRIBER, ActivityInput, ActivityEventData |
Every exported type that ended in Args or Settings is a *Options,
*Params or *Values type with no alias: AddArgs is AddOptions,
ListArgs is ListParams, CliArgs is CliValues, RateLimitSettings is
RateLimitOptions, AccountingSettings is AccountingSetup and
BucketSettings is StorageBucketOptions. The full table is in the
changelog.
better-supabase/config no longer exports the generators (zod, valibot,
jsonSchema, toJsonSchema, standardSchema, factories, nuqs). Import each
one from better-supabase/generators/<name>; codemod generators rewrites the
imports and lists any generator that still uses the apiVersion: 1 shape.
better-supabase/server no longer exports the account helpers, the CSV helpers
or the rate-limit header helpers. Import them from
better-supabase/server/accounts, better-supabase/server/csv and
better-supabase/server/rate-limit; codemod server-imports splits each import.
Generators are plugins at apiVersion: 2, and zod 4.x and valibot 1.x are
optional peers with a tested range.
Three events use the lifecycle verbs:
| Old | New |
|---|---|
vex_statement.saved | vex_statement.updated |
team_member.removed | team_member.deleted |
reaction.removed | reaction.deleted |
Blocks
| Old | New |
|---|---|
better-supabase/blocks/time-tracking | better-supabase/blocks/time-entries |
blocks["time-tracking"], db.$blocks.timeTracking | blocks["time-entries"], db.$blocks.timeEntries |
createTimeTracking, timeTrackingBlock, timeTrackingEvents | createTimeEntries, timeEntriesBlock, timeEntriesEvents |
time_tracking.read, .track, .approve, .manage | time_entries.read, .track, .approve, .manage |
better-supabase/blocks/bulk | better-supabase/blocks/bulk-actions |
blocks.bulk, db.$blocks.bulk | blocks["bulk-actions"], db.$blocks.bulkActions |
createBulk, bulkBlock, bulkEvents | createBulkActions, bulkActionsBlock, bulkActionsEvents |
bulk.run, bulk.* | bulk_actions.run, bulk_actions.* |
upgrade writes the migration that renames the registry row and the permission
keys in place, so roles and overrides keep their grants. The bulk run keeps its
names: the bulk_runs table, start_bulk, bulk_run.completed and the
BULK_* hint codes. The rank block keeps its name (ADR 0393).
The tasks block's default entity is task, not work_item. Its functions take
a task argument, its events carry taskId, its subject paths start with
tasks/ and the goal metric is tasks.completed. An app that names the entity
work_item in blocks.tasks.names keeps its tables and functions.
The vector-search block installs itself for an app with a vectorSearch table
or a -- @bsb vector-search tag. List it only to set its schema option.
Invoice numbers
The invoices table is unique on organization, direction, kind and identifier (or
the number when the table has no identifier column), in every project.
blocks.invoices.options.uniqueNumber is removed, and the config refuses it.
Delete the option. A migration that adds the key fails when two invoices of one
kind share an identifier. Find them first:
select tenant_id, direction, kind, coalesce(identifier, number) as identifier, count(*)
from better_supabase.invoices
group by 1, 2, 3, 4
having count(*) > 1;Use your own table and column names if you map the block to your table.
Give each row a distinct identifier, or renumber the duplicates, before you
apply the migration.
Database names
codemod naming writes the migration for an installed app. No data moves, and
the upgrade that follows replaces the functions that read the new names.
| Old | New |
|---|---|
organization_id on block tables | tenant_id |
<role>_user_id (owner_user_id, reviewer_user_id, actor_user_id) | <role>_id (owner_id, reviewer_id, actor_id) |
deleted_at on drive_nodes, memories, messages | archived_at |
locked_until, leased_until, snoozed_until, paused_until, claimed_until | lock_expires_at, lease_expires_at, snooze_expires_at, pause_expires_at, claim_expires_at |
scheduled_for, purge_after, not_before, effective_from | scheduled_at, purge_at, not_before_at, effective_at |
valid_from, valid_until on passes and prices | starts_at, expires_at |
engagement_subjects, engagement_recordings, engagement_notes, engagement_transcript_segments | activity_subjects, activity_recordings, activity_notes, activity_transcript_segments |
stream_kv, invoice_e_endpoints, workflow_step_library | stream_entries, e_invoice_endpoints, workflow_step_templates |
bsb.uid(), bsb.owns(uuid), bsb.is_member(uuid), bsb.can(uuid, text) | the same functions in better_supabase |
bsb.recorded, bsb.lane_move (set_config keys) | better_supabase.recorded, better_supabase.lane_move |
Constraints, triggers and policies that a block generates carry a bsb_
prefix: policies are bsb_<name>, triggers are bsb_*, check constraints are
bsb_<table>_<column>_check, unique constraints end in _key and exclusion
constraints in _excl. Apps that adopt their own tables keep their column names and map
tenant in columns; memberships.organization_id and auth.users.deleted_at
do not change. The full column list is in the changelog.
Migrations the CLI writes are named <stamp>_better_supabase_<name>_<hash>.sql.
The stamp is later than every migration in the folder, and migrations from
earlier releases are still found by their suffix.
Declarative module files
The files upgrade writes are declarative DDL: no drop ... if exists, no
if not exists on tables, indexes or the columns of tables a block owns, and no
do block that creates objects. A schema diff reads each file as the end state.
Shared helpers sit once in 900_better_supabase_000_prelude.sql, and event
triggers and triggers on auth tables sit in
900_better_supabase_999_unscoped.sql. Row audit triggers are static
create constraint trigger bsb_audit statements in each block's file.
Run better-supabase upgrade, commit the files, then run your SQL workflow's
sync. The migrations workflow still writes statements that can run again.
better-supabase upgrade --check fails on transaction=false, a top-level
insert, an object two files declare and duplicate policies.
powersync streams also declares the powersync publication in
supabase/schemas/powersync_publication.sql when the repo has that folder, so
declarative sync --strict-coverage stays empty. Run it again and commit the
file.
Doctor has new rules for these files: BS365 to BS368 (Supabase practices), BS369 to BS377 (declarative coverage), BS378 to BS380, BS381 and BS382 (projection bypass) and BS620 to BS622.
Your own columns and tables
A column that your schema file adds to a block's table (alter table ... add column) is a field of that block. The extraColumns option and its prefixed
forms are removed from every block, and codemod extra-columns moves them into
the file that alters the table, or into <table>.ext.sql. For a table you
create, list the columns in its comment as @bsb fields plan, seats.
codemod owned-columns declares the columns blocks add to tables you own as
<table>.block-columns.sql. Blocks no longer add columns to a table you create:
a feature whose column your table lacks is off (ADR 0580). Before you run
upgrade, run better-supabase codemod owned-columns, commit the
*.block-columns.sql files, and check better-supabase doctor for BS622. The
statements are the blocks' own, so a database that has the columns changes
nothing, and a database that lacks one keeps the feature off until you declare
it.
blocks.memberships.options.sameTenant defaults to the foreign keys your schema
declares. upgrade writes a bsb_same_tenant_<column> trigger for each
single-column key between two tenant tables; set sameTenant: false to write
none.
Generated client code
gen writes one module per table and one slice module per block by default.
The server's schema is generated-schema.ts, and generated.ts exports
clientSchema for a browser client.
- Run
better-supabase gen, thenbetter-supabase codemod table-modules. It importsschemafromgenerated-schemaand reads tables in"use client"files through the table modules. - Build the browser's client over
clientSchema, register the tables it uses and pass the block slices it uses tocreateClient({ blocks }). - To keep the single module, set
gen.tableModules: falseandgen.blockModules: false.
The Vue, Solid and Svelte hooks read through the table module like the
React hooks do, and doctor BS621 warns when a "use client" file imports
generated-schema, blocks.generated or blocks.config. Frameworks are
registrations: defineFramework from better-supabase/plugins builds a
binding, and gen.hooks takes any registered name.
CLI
CLI contract 3 follows the Vercel CLI guidelines.
| Old | New |
|---|---|
| exit code 3 | exit code 2 with userActionRequired: true |
--confirm <token> | --yes --effects-digest <digest> |
consent_required, error.token | confirmation_required, data.digest |
error.code, error.detail on stdout | reason, message on stderr |
cli-result-v1.json, cli-commands-v1.json | cli-result-v2.json, cli-commands-v2.json |
db seed --apply | db seed --yes |
doctor --fix | doctor --yes |
dev env --write | dev env --out |
gen --emit <file> | gen --metadata <doc> --only <file> |
gen --out-dir | gen --metadata <doc> --out <dir> |
gen --watch --interval 500 | dev --interval 500 |
better-supabase db prune-workflows | better-supabase workflows prune |
An old flag exits 2 with flag_renamed and the replacement, so CI tells you
which line to change. Search your scripts, CI files and cron entries for the old
forms. better-supabase cache purge [--dry-run] deletes the CLI cache.
Removed
- The 0.46 and 0.47 rename shims. Permission keys, event types, hint codes, subject types and entity keys from before those releases no longer resolve.
- The module upgrades for the tables 0.47 renamed. Upgrades start at 0.53.
$apiResult, thetriggerSqloverload, the moduleversionoption, theopenapiconfig key (usespecs), thecanApprovetemplate andapprovals.distinctApprover,BLOCK_EVENT_RENAMESand the 0.15 block renames.
Stays as it is
- Rows keep the configured casing, and
$clientandqueryRawuse database names. - The generated file layout (
schema.ts,tables/,blocks/<instance>.ts) does not change in 0.58. ADR 0393 lists what is deferred: reads through a view named after a block instance, child PowerSync streams, the owned-column default flip and the drivegrantsmode. - Cloud design URLs that you saved before this release carry the old
integrationsfield. Ask the cloud app to open and save the design again, or rename the field toadaptersin the design file.
Last updated on
From 0.53 to 0.54
What to check when upgrading from better-supabase 0.53 to 0.54. Doctor BS315 is an error, tests.authenticate_as looks users up through security definer helpers, withContext takes an async factory, and the tasks module gains set forms. Notes on db.$decode and single-row misses (PGRST116).
From plain supabase-js
Move an app that calls supabase.from() directly to typed repositories, one query at a time.