# From 0.5 to 0.6

> What to change when upgrading to better-supabase 0.6, which renames kits to blocks, moves the feature modules under better-supabase/blocks, spells out organization and replaces the PermDock settings with authorization providers.

Source: https://bettersupabase.com/docs/migration/0.5-to-0.6

0.6 renames kits to blocks and moves every feature module under
`better-supabase/blocks/<name>`. Names that said `org` now say `organization`,
in TypeScript, in SQL and in event types. The PermDock settings become one
neutral [authorization provider](/docs/extending/authorization-providers).
This release is a clean break: the
old names have no aliases or SQL wrappers, so update imports, config and SQL in
one change.

## Update imports [#update-imports]

| 0.5                                                                        | 0.6                                                               |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `createOrgs` from `better-supabase/orgs`                                   | `createOrganizations` from `better-supabase/blocks/organizations` |
| `better-supabase/jobs` (queue, cron, idempotency, inbox)                   | `better-supabase/blocks/jobs`                                     |
| `createOutbox`, `outboxCloudEvent` from `better-supabase/jobs`             | `better-supabase/blocks/outbox`                                   |
| `purgeAuditLog`, `PurgeAuditLogOptions` from `better-supabase/jobs`        | `better-supabase/blocks/audit`                                    |
| `createInbox` and the `Inbox*` types from `better-supabase/jobs`           | `createWebhookInbox` and `WebhookInbox*` from `blocks/jobs`       |
| `entitlementMembers`, `ENTITLEMENTS_UPDATED` from `jobs`                   | `better-supabase/blocks/entitlements`                             |
| `hasEntitlement`, `EntitlementKey` from `server`, `next`, `ssr` or `react` | `better-supabase/blocks/entitlements`                             |
| `better-supabase/notifications`                                            | `better-supabase/blocks/notifications`                            |
| `useNotifications` from `better-supabase/react`                            | `better-supabase/blocks/notifications/react`                      |
| `better-supabase/webhooks`                                                 | `better-supabase/blocks/webhooks`                                 |
| `orgLogoBucket`                                                            | `organizationLogoBucket`                                          |

`Org` names become `Organization` names, `Kit` names that describe a feature
become `Block` names, and `Kit` names that describe a SQL module become
`Module` names. The rest of `better-supabase/orgs`, `/notifications` and
`/webhooks` keeps its names under the new subpath (`Invitation`,
`SwitchResult`, `createWebhooks`, `signWebhook` and so on). Every export that
0.6 renames or removes:

| Subpath                             | 0.5.1                                                                       | 0.6                                                                                                                   |
| ----------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `.`                                 | `KitEvent`, `KitEventMap`, `KitEventType`                                   | `BlockEvent`, `BlockEventMap`, `BlockEventType`                                                                       |
| `events`                            | `onKitEvent`, `forwardKitEvents`, `ForwardKitOptions`                       | `onBlockEvent`, `forwardBlockEvents`, `ForwardBlockOptions`                                                           |
| `events`                            | `KIT_ATTRIBUTES`, `kitCloudEvent`, `kitEventAttributes`                     | `BLOCK_ATTRIBUTES`, `blockCloudEvent`, `blockEventAttributes`                                                         |
| `events`                            | `KitEventMeta`, `KitEventPattern`, `KitEventsMatching`                      | `BlockEventMeta`, `BlockEventPattern`, `BlockEventsMatching`                                                          |
| `events`                            | `OrgEventData`                                                              | `OrganizationEventData`                                                                                               |
| `otel`                              | `traceKitEvents`                                                            | `traceBlockEvents`                                                                                                    |
| `orgs`                              | `createOrgs`, `Orgs`, `OrgsOptions`, `CreateOrgOptions`, `OrgAttributes`    | `createOrganizations`, `Organizations`, `OrganizationsOptions`, `CreateOrganizationOptions`, `OrganizationAttributes` |
| `orgs`, `notifications`, `webhooks` | `KitTransport`                                                              | `BlockTransport`                                                                                                      |
| `storage`                           | `orgLogoBucket`, `OrgLogoBucketOptions`                                     | `organizationLogoBucket`, `OrganizationLogoBucketOptions`                                                             |
| `config`                            | `KitsConfig`, `KitModuleConfig`, `KitMode`, `AccessKitConfig`               | `ModulesConfig`, `ModuleConfig`, `ModuleMode`, `AccessModuleConfig`                                                   |
| `sql`                               | `renderKit`, `kitLayout`, `kitPermissionKeys`, `kitDeprecations`            | `renderModules`, `moduleLayout`, `modulePermissionKeys`, `moduleDeprecations`                                         |
| `sql`                               | `kitFilePaths`, `kitFileVersion`, `sameKitFile`                             | `moduleFilePaths`, `moduleFileVersion`, `sameModuleFile`                                                              |
| `sql`                               | `kitIdType`, `isKitIdType`, `KIT_ID_TYPES`, `KitIdType`                     | `moduleIdType`, `isModuleIdType`, `MODULE_ID_TYPES`, `ModuleIdType`                                                   |
| `sql`                               | `KitContext`, `KitLayout`, `KitFile`, `KitNames`, `KitTableSpec`            | `ModuleContext`, `ModuleLayout`, `ModuleFile`, `ModuleNames`, `ModuleTableSpec`                                       |
| `sql`                               | `KitContractFunction`, `KitDeprecation`, `KitPermissionKey`                 | `ModuleContractFunction`, `ModuleDeprecation`, `ModulePermissionKey`                                                  |
| `sql`                               | `KitUpgrade`, `KitUpgradePlan`, `InstalledKitModule`                        | `ModuleUpgrade`, `ModuleUpgradePlan`, `InstalledModule`                                                               |
| `sql`                               | `KitAccessPermdock`, `KitPermdock`                                          | `ModuleAccessProvider`, `ModuleEntitlementsProvider`                                                                  |
| `sql`, `storage`, `realtime`        | `PermdockCatalog`                                                           | `AuthorizationProvider` from `better-supabase/config`                                                                 |
| `storage`, `realtime`               | `PermdockBucketPolicy`, `PermdockTopicPolicy`                               | `AccessBucketPolicy`, `AccessTopicPolicy`                                                                             |
| `config`                            | `PermdockPathsConfig`                                                       | removed; see [Move to an authorization provider](#move-to-an-authorization-provider)                                  |
| `sql`                               | `PERMDOCK_SCHEMA`, `permdockKeys`, `permdockKeyStatus`, `PermdockKeyStatus` | removed; the provider lists its permissions                                                                           |

`list`, `storage`, `realtime` and `events` keep their subpaths.

## Update the config [#update-the-config]

`sql.kit` (the modules to keep in sync) and the `kits` key (their settings)
merge into one key, `sql.modules`. It takes an object keyed by module name,
where every key is a module to keep in sync and its value is that module's
settings. A list of names still works when no module needs settings.

```ts title="better-supabase.config.ts"
export default defineConfig({
  sql: {
    modules: {
      access: { roles: ["owner", "admin", "member"] },
      organizations: {},
      invitations: {},
    },
  },
});
```

## Update event types [#update-event-types]

The organization events are now `organization.created`,
`organization.updated`, `organization.deleted`, `organization.role_changed`,
`organization.member_added`, `organization.member_removed`,
`organization.member_left`, `organization.ownership_transferred` and
`organization.switched`. Update `sb.on` patterns, outbox consumers and
webhook subscriptions that name an `org.*` type.

Every event type is now `<entity>.<past_tense_verb>`, with a snake\_case
entity and no third segment. These types were renamed:

| 0.5                                               | 0.6                                               |
| ------------------------------------------------- | ------------------------------------------------- |
| `org.*`                                           | `organization.*`                                  |
| `ai_chat.message.completed`                       | `ai_chat_message.completed`                       |
| `inbox.conversation.*`                            | `inbox_conversation.*`                            |
| `inbox.message.received`                          | `inbox_message.received`                          |
| `incoming_webhook.rotated`                        | `incoming_webhook.token_rotated`                  |
| `workflow.alert`                                  | `workflow_alert.triggered`                        |
| `workflow.run.completed`, `.failed`, `.cancelled` | `workflow_run.completed`, `.failed`, `.cancelled` |
| `data_export.ready`                               | `data_export.completed`                           |

`BLOCK_EVENT_RENAMES` from `better-supabase/events` maps each old
organization type to its new name, for a consumer that reads events written
before the upgrade. The full list of types and their data keys is on the
[events page](/docs/extending/events#block-events).

Payloads and subjects changed too:

* `support.started` and `support.ended` carry a camelCase payload
  (`sessionId`, `adminId`, `targetUserId`, `reason`, `readOnly`,
  `expiresAt`, `organizationId`, and `endedBy` on `support.ended`), and their
  subject is `support-sessions/<id>`.
* Subjects are kebab-case plurals: `push-devices/<id>`,
  `waitlist-entries/<id>` and `audit-entries/<id>`.
* `scim.user_*` and `scim.group_*` name the SCIM resource `scimUserId` or
  `scimGroupId` instead of `id`.
* `support.*`, `notification.created`, `webhook.disabled` and the workflow
  events carry `organizationId`.
* `notification.created`, `webhook.disabled`, `attachment.scanned`,
  `inbox_message.received`, `data_export.completed`, `data_export.failed` and
  `organization.purged` have an idempotency key, so a retried call writes
  one event.

## Regenerate the SQL modules [#regenerate-the-sql-modules]

The module files now start with `-- @bs-module` (`-- @bs-module-data` for the
data files), and the module registry table `better_supabase.kit_modules` is
now `better_supabase.modules`. These SQL functions were renamed:

| 0.5                                            | 0.6                                              |
| ---------------------------------------------- | ------------------------------------------------ |
| `member_org_ids(roles)`                        | `member_organization_ids(roles)`                 |
| `has_org_role(org, roles)`                     | `has_organization_role(organization, roles)`     |
| `org_member_role(org, member)`                 | `organization_member_role(organization, member)` |
| `org` parameters of the organization functions | `organization`                                   |

Update your policies and RPCs that call them, then rewrite the module files and
create the migrations:

```bash
better-supabase sql sync
supabase db schema declarative sync
better-supabase sql data
```

The schema diff drops `kit_modules` and the old functions and creates the new
ones, and `sql data` writes the `modules` rows into a migration after it.
A function whose parameter was renamed can't be replaced in place, so apply
the files through the diff rather than by running them on the database.

## Move to an authorization provider [#move-to-an-authorization-provider]

better-supabase no longer reads `permdock.config.ts`, `permdock.manifest.json`
or `permissions.catalog.json`. The `authorization` key takes a provider
object instead, and PermDock builds it from those files with
`authorizationProvider({ manifest, catalog })` from `permdock/better-supabase`:

```ts title="better-supabase.config.ts"
import { readFileSync } from "node:fs";
import { defineConfig } from "better-supabase/config";
import { authorizationProvider } from "permdock/better-supabase";

const read = (file: string) =>
  readFileSync(new URL(file, import.meta.url), "utf8");

export default defineConfig({
  authorization: authorizationProvider({
    manifest: read("permdock.manifest.json"),
    catalog: read("permissions.catalog.json"),
  }),
  sql: { modules: { access: { model: "provider" } } },
});
```

| 0.5                                                                | 0.6                                                                                                      |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `permdock: { manifest, catalog }` in the config                    | `authorization: authorizationProvider({ manifest, catalog })`                                            |
| `sql.modules.access.model: "permdock"`                             | `sql.modules.access.model: "provider"`                                                                   |
| `sql.modules.access.permdock.schema`, `.scope` and `.forUser`      | the provider's `functions`, `tenantScope` and the `idsWithFor` and `isPlatformFor` templates             |
| `entitlements.permdock: { scope }`                                 | the provider's `tenantScope`                                                                             |
| `entitlements.permdock: false`                                     | `entitlements.memberships: "tenant"`                                                                     |
| `policy: { permdock: { read, write }, scope, schema }` on a bucket | `policy: { access: { read, write }, scope, sql }`, with `bucketPolicy()` from `permdock/better-supabase` |
| `permdock: { receive, send }` on a topic                           | `access: { receive, send, scope, sql }`, with `topicPolicy()` from `permdock/better-supabase`            |
| `catalog` on `defineBucket` and `defineTopic`                      | the provider's `permissions` (`sqlComplete`), checked by `gen` and doctor BS214                          |
| `permdockVerifier` from `better-supabase/blocks/api-keys`          | `apiKeyVerifier` from `permdock/better-supabase`                                                         |
| `permdock` on `apiKeyResolver` and `apiKeyClaims`                  | `claim`, or `apiKeyClaimOptions(manifest)` from `permdock/better-supabase`                               |
| `options.scopes: "catalog"` on the api-keys module                 | unchanged; it reads the provider's `permissions`                                                         |
| `PermdockBucketPolicy`, `PermdockTopicPolicy`, `PermdockCatalog`   | `AccessBucketPolicy`, `AccessTopicPolicy`, `AuthorizationProvider`                                       |
| `KitAccessPermdock`, `KitPermdock`                                 | `ModuleAccessProvider`, `ModuleEntitlementsProvider`                                                     |
| `PERMDOCK_SCHEMA`, `permdockKeys`, `permdockKeyStatus`             | removed; the provider lists its permissions                                                              |

The access policies keep the key lists they had. Without `sql`, they call the
access contract (`tenant_ids_with`, `is_platform`), so a bucket or topic on
the tenant scope works under every access model. Doctor's BS107, BS213,
BS214, BS324, BS404, BS405, BS407 to BS409 and BS411 keep their codes and
read the provider; their titles no longer name PermDock.

Run `better-supabase sql sync` and `better-supabase gen` after the change. The
rendered SQL calls the same functions as before when the provider builds the
same templates.

`better-supabase sql add tenant` refuses to write the module when the
provider's access token hook already writes the memberships claim, since the
two hooks would disagree about it. Use the provider's hook, or pass `--force`
to write the module anyway.

## Removed deprecations [#removed-deprecations]

The aliases that 0.5 kept for one minor are gone:

| Removed                                                    | Use                                                                                                                 |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| A support token whose `act` has `session_id` but no `kind` | tokens minted by 0.5.1 or later (`act.kind: "support"`); older ones are refused as `invalid-chain`                  |
| The read-only `better_supabase.audit_log` view             | `better_supabase.audit_events` (`occurred_at`, `organization_id`); the audit module's version 5 step drops the view |
| `maxUrlLength` on `defineSupabase` and `postgrestExecutor` | `urlLengthLimit`                                                                                                    |
| `scopes` on the MCP server options                         | `advertisedScopes`                                                                                                  |

## Other breaking changes [#other-breaking-changes]

* A bucket definition's `path(values)` and a connected bucket's `publicUrl()`
  return a `Result` instead of throwing a `DbException`. Read `.data` (`null`
  on an error) or check `.ok`.
* `db.$rpc` returns table rows (`returns setof customers`) and
  `returns table (...)` records in the configured casing, with codecs
  applied. Remove a snake-to-camel mapping of the result, or pass
  `{ raw: true }` to keep database names.
* `gen` types function results as nullable: a scalar result, each element of
  a `setof` scalar, a single row and each `returns table` column are
  `| null`. Set `functions.<name>.notNull` in `better-supabase.config.ts`
  when a result is never null.
* `notifications.markRead`, `markUnread`, `dismiss` and `resolve` return
  `{ count, items }` instead of a number. `notifications.send()` returns
  `{ id, recipients }`, or `null` when nobody was left.
* `notifications.list` pages with `cursor` instead of `before`. It takes the
  same values: the last item of the previous page, or an instant.
* The notifications, organizations and outgoing webhooks blocks take
  `mappers` instead of `errorMappers`.
* The leaf entry that runs after `withSupabase` is now
  `withBetterDb(betterSupabase)`. `withBetterSupabase(server)` is one entry
  that resolves the caller itself, so you can drop `withSupabase`.
* The webhook inbox is `createWebhookInbox` from `better-supabase/blocks/jobs`;
  `createInbox` from `better-supabase/blocks/inbox` is the conversation inbox.
* The server's `auth.kind` gains `"apiKey"`, and `DbError` gains the
  kinds `quota_exceeded` (HTTP 429), `max_affected` (400) and `unsupported`
  (501). An exhaustive `switch` over either needs the new cases.
* `deleteMany` and `updateMany` refuse a `where` that filters nothing. Pass
  `allowAll: true` to `updateMany` to update every row. An update whose
  `data` sets no columns returns `invalid_request` instead of `not_found`.
* A `*` in a `like` or `ilike` pattern is a literal character; use `%`.
* List queries count with `count: "planned"` by default. Pass
  `count: "exact"` where you show an exact total.
* Cursors record the table and sort, so a cursor stored before 0.6 or an
  array from `encodeCursor()` fails with `Invalid cursor`. Start again with
  `after: null`.
* `createEdge`'s `cors` option runs `withCors` from
  `@supabase/middleware/cors`: a preflight needs
  `Access-Control-Request-Method`, and an allow-list adds `Vary: Origin`.
* `gen` names colliding `_by_` relations by every key column
  (`customerByCustomerOrganization`) instead of ending them in `_`, and view
  copies are no longer relations. Keep an old name with
  `tables.<table>.relations`.
* `@supabase/postgrest-typegen` is an optional peer. Install it for `gen`,
  `introspect` and `doctor` with
  `pnpm add -D @supabase/postgrest-typegen@0.4.0`. A `GeneratorInput` built
  by hand needs a `model`.
* `Invitation` has a required `inviter` field (`null` without the profiles
  module).
* `better-supabase/next` needs Next.js 16.3 or later (`next` peer
  `>=16.3 <17`). Upgrade Next.js first.
* `better-supabase` depends on `@supabase/server` `^1.9.1`; the framework
  bridges replace its framework adapters.

## SQL behaviour changes [#sql-behaviour-changes]

These apply once `better-supabase sql sync` rewrites the module files:

* A table in `expose` loses privileges it doesn't list, including
  `truncate`, `references` and `trigger`.
* Idempotency keys have a holder: `begin_idempotent` returns `holder`, and
  `complete_idempotent` and `release_idempotent` take it.
* An invitation past its expiry fails with `INVITATION_EXPIRED` (accept) and
  can no longer be edited with `update_invitation`; call
  `resend_invitation` first.
* `roleThrough.where` and `platformRoles.through.where` hold for direct
  writes too, through `bs_role_scope` triggers.
* `mark_notifications_read` and the other notification state functions
  return the changed notifications with the count.
* `inbox` no longer installs `jobs` and `streams`, `workflows` no longer
  installs `jobs`, and `ai-cache` no longer installs `tenant`. Keep them in
  `sql.modules` when you use them: without `jobs`, the inbox queues no bot
  or delivery jobs.
* `webhooks-in` installs `updated-at`.
* `support.ended` carries the session's tenant in its audit entry and outbox
  event.
* Module actions write their audit entries under the shared categories
  (`membership`, `access`, `security`, `configuration`, `billing`, `data`,
  `ai`, `integration`; see [Audit](/docs/blocks/audit#module-actions)).
  Organization entries were `organization`, support entries `support` and
  revealed details `audit`. An adopted log with a category check maps the new
  names with `sql.modules.audit.options.values.category`.
  `options.auditCategory` is deprecated.
* Settings, flags, billing customers, credentials, connectors, agents,
  provider keys, tool policies and approvals, chat shares, incoming webhooks,
  webhook secrets, announcements, published workflows and revealed audit
  details now write an audit entry and an outbox event, as do the access
  catalog's tables (audit entry only). Set `audit: false` on a module to
  leave it out of the log.
* `support.started` carries the session's tenant in its audit entry and
  outbox event.
* `sql.modules.jobs.schema` is an error. Remove it: the jobs functions were
  always in `better_supabase`.

## Run the codemod [#run-the-codemod]

`better-supabase codemod 0.6` renames the `Kit` and `Org` exports from the
rename table where your code imports them, `maxUrlLength` to
`urlLengthLimit`, and string literals that are exactly a renamed event type
(`"org.member_added"`, `"org.*"`) to the new name. It lists the lines it can't rewrite for review:
`$rpc` calls, `publicUrl()` and `path()` calls, and imports from the removed
subpaths. It does not move an import to its new subpath, rewrite SQL or
change config keys. Run it after `better-supabase gen`, then fix what the
type checker reports.