From 0.1 to 0.2
What to change when upgrading to better-supabase 0.2, which renames claims and SQL functions to one claim contract.
0.2 renames claims and SQL functions to one claim contract, shared with authorization libraries that write the same claims. There are no compatibility options: follow the steps below, regenerate, and run your tests.
Claims
The active tenant claim is tenant_id instead of org_id, in the token and
in app_metadata. Update your custom access token hook, anything that sets
app_metadata.org_id through the Auth admin API, and your claims schema:
export const Claims = v.looseObject({
tenant_id: v.optional(v.pipe(v.string(), v.uuid())),
app_metadata: v.optional(
v.looseObject({ tenant_id: v.optional(v.pipe(v.string(), v.uuid())) }),
),
});To keep another name, set it once in better-supabase.config.ts instead of
per plugin. plugins.tenant.claim is gone:
export default defineConfig({
claims: { tenant: "org_id" },
});Use loose objects (valibot looseObject, zod z.looseObject) for claims
schemas, so claims your schema doesn't list reach the code that reads them.
SQL kit
Run better-supabase sql sync (or sql add again) after upgrading. The
modules change as follows:
| 0.1 | 0.2 |
|---|---|
better_supabase.current_org_id() | better_supabase.current_tenant_id() |
membership_claims() in the entitlements module, [{ tenant_id, roles, entitlements }] | membership_claims() in the tenant module, [{ scope, id, roles }] |
| Plan features inside each membership | feature_claims() in the entitlements module, { [tenantId]: string[] } in the features claim |
Replace current_org_id() in your own policies and functions. A hook that
used membership_claims() now sets two claims:
claims := jsonb_set(claims, '{memberships}', better_supabase.membership_claims(uid));
claims := jsonb_set(claims, '{features}', better_supabase.feature_claims(uid));sql add tenant stops when another access token hook owns the memberships
claim. Pass --force to write it anyway. Since 0.6 that hook is declared by
an authorization provider; see
From 0.5 to 0.6.
TypeScript
MembershipClaimhas the shape{ scope, id, roles }(and optionalwithin,via,expiresAt). Code that readtenant_idorentitlementsfrom a membership readsidand thefeaturesclaim.hasEntitlement(session, tenantId, key)reads thefeaturesclaim. Pass the claim name as a fourth argument if you renamed it.EntitlementKeyinfers keys from thefeaturesclaim of your claims schema.AuthSession,AuthStateandBetterSupabasetake one more type parameter, the profile type from the newbetterSupabase.userMetadata(schema). It defaults tounknown, so existing annotations keep compiling. If you read display fields fromsession.claims.user_metadata, move them to the typedsession.profile. It is for display only: users can change it withauth.updateUser(), so roles, memberships, the tenant and entitlements never come from it.
Storage and Realtime
Bucket and topic policies with policy: 'tenant' compare the configured
tenant claim, so they read tenant_id after sql sync and gen. Buckets
and topics can also check permissions: see
storage.
Testing
asUser signs ES256 tokens with the key better-supabase keys writes,
the way a hosted project signs them. Create the key, set signing_keys_path
in supabase/config.toml and restart the stack (signing key).
To keep signing with the shared JWT secret, pass { alg: 'HS256' }; it only
works against a local stack. Tests that used localAuth(secret) to accept
test tokens can drop it, since ES256 tokens verify against the stack's JWKS.
Doctor
- BS405 measures the whole token against 2 KB, and an authorization hook's
own budget as a separate warning.
doctor.claimsLimitchanges the limit. - BS407 is new: next to another authorization hook, it reports a hook that
calls
membership_claims()or writes the claims that hook owns. Thefeaturesclaim is not reported.
MCP
SPEC_PINS.mcp and MCP_PROTOCOL_VERSION are 2026-07-28. createMcp
serves those stateless requests and still answers initialize for
2025-11-25, 2025-06-18 and 2025-03-26 clients. A 403 now carries an
insufficient_scope challenge; set scopes to publish the scopes your tools
need (MCP servers).
Last updated on