Overview
Feature modules for SaaS apps, each a set of SQL modules in your schema with an optional TypeScript side under better-supabase/blocks.
A block is a feature most SaaS apps build by hand: organizations and
invitations, background jobs, notifications, outgoing webhooks, plan
entitlements. Each block installs one or more SQL modules
into supabase/schemas with better-supabase sql add, and blocks with a
TypeScript side import it from better-supabase/blocks/<name>. Blocks that are
SQL only have no subpath; you call their functions from policies and RPCs.
pnpm better-supabase sql add organizations invitationsimport { createOrganizations } from "better-supabase/blocks/organizations";To build several blocks with one set of options, and wire the blocks that
use each other, use createBlocks from
better-supabase/blocks.
A block can be extended without forking it: add your own columns and type them with a Standard Schema, steer its methods with hooks in TypeScript or SQL, wrap its transport with middleware and add methods. See Extending blocks.
List the modules to keep in sync in sql.modules in better-supabase.config.ts,
keyed by module name, with each module's settings as the value. The primitives that every app
uses, such as list queries,
storage and realtime topics,
are not blocks and keep their top-level subpaths.
Connections
A block's TypeScript side runs its SQL functions through a connection you pass in. It never opens a pool of its own.
| Connection | What it runs as | Blocks |
|---|---|---|
ctx.postgresAdmin from withPostgresAdminClient (@supabase/server) | The connection-string role, bypassing RLS | jobs, idempotency, webhook inbox, outbox, audit, webhooks out |
ctx.postgres from withPostgresClient | The caller, with RLS | organizations, notifications (through sqlTransport) |
createPostgres() from better-supabase/postgres | admin, or a user through asUser(claims) | every block |
rpcTransport(supabase) over PostgREST | The client's session, or the secret key | organizations, notifications, webhooks out |
Every function that takes a SQL client accepts @supabase/server's Postgres
clients as they are, so an app that already composes withPostgresClient and
withPostgresAdminClient keeps one pool and one connection string
(SUPABASE_DB_URL by default, or connectionString). withBlock from
better-supabase/server builds a block on the pipeline context; see
middleware.
@supabase/server runs each query in its own transaction. A block call is
one SQL function, so that changes nothing for the blocks, but
outbox.emit() then commits on its own instead of with the writes before it.
Emit from SQL (emit_event inside the function that writes) when the event
must commit with the write.
A SQL connection needs TCP: Node, Deno, Bun and the Supabase Edge runtime have
it, Cloudflare Workers and other isolates without sockets do not. There, use
rpcTransport for the blocks that call only SQL functions (organizations,
notifications, outgoing webhooks, flags, announcements and the rest of the
blocks that take a transport, and createOutbox, which takes a transport in
place of the SQL client), and the pgmq_public RPCs for jobs. The
TypeScript helpers of the idempotency keys, the webhook inbox, incoming
webhooks, route rate limits and exportAuditLog still need a SQL connection,
but every function a module grants, the service-role ones of jobs,
idempotency, webhook-inbox and access included, gets an entry point in
the API schema, so a server can call them over the Data API. Give
the module an API schema (sql.modules.<module>.api, see
SQL modules), expose
that schema, and pass it as rpcTransport(supabase, { schema: "api" }). Never
expose the module schema itself.
Blocks
| Block | Subpath | SQL modules |
|---|---|---|
| Access contract | none | access, tenant |
| Organizations and invitations | better-supabase/blocks/organizations | organizations, invitations |
| Profiles | better-supabase/blocks/profiles | profiles |
| Audit log | better-supabase/blocks/audit | audit |
| Jobs, idempotency and webhook inbox | better-supabase/blocks/jobs | jobs, idempotency, webhook-inbox |
| Outbox | better-supabase/blocks/outbox | outbox |
| Durable streams | better-supabase/streams, better-supabase/streams/redis | streams |
| Workflows | better-supabase/blocks/workflows, better-supabase/blocks/workflows/react | workflows |
| Workflow SDK | better-supabase/workflow-sdk, better-supabase/workflow-sdk/world | workflow-sdk-world |
| Workflow builder | better-supabase/blocks/workflow-builder, better-supabase/blocks/workflow-builder/react, better-supabase/workflow-sdk/builder | workflow-builder |
| Notifications | better-supabase/blocks/notifications, better-supabase/blocks/notifications/react | notifications |
| Inbox | better-supabase/blocks/inbox, better-supabase/blocks/inbox/react, better-supabase/chat-sdk | inbox, chat-sdk-state |
| Outgoing webhooks | better-supabase/blocks/webhooks | webhooks-out |
| Incoming webhooks | better-supabase/blocks/webhooks | webhooks-in, webhook-inbox |
| API keys | better-supabase/blocks/api-keys | api-keys |
| Settings | better-supabase/blocks/settings | settings |
| Usage and quotas | better-supabase/blocks/usage | usage |
| Billing | better-supabase/blocks/billing | billing |
| Feature flags | better-supabase/blocks/flags | flags |
| Comments and activity | better-supabase/blocks/comments | comments |
| Attachments | better-supabase/blocks/attachments | attachments |
| Data lifecycle | better-supabase/blocks/data-lifecycle | data-lifecycle |
| SSO and SCIM | better-supabase/blocks/sso | sso |
| Onboarding | better-supabase/blocks/onboarding, better-supabase/blocks/onboarding/react | onboarding |
| Waitlist and invite codes | better-supabase/blocks/waitlist | waitlist |
| Announcements | better-supabase/blocks/announcements, better-supabase/blocks/announcements/react | announcements |
| Entitlements | better-supabase/blocks/entitlements | entitlements |
| Vector search | none (db.$search in the core) | vector-search |
| AI chat | better-supabase/blocks/ai-chat, better-supabase/blocks/ai-chat/react | ai-chat |
| AI files | better-supabase/blocks/ai-files | ai-files |
| Knowledge | better-supabase/blocks/knowledge | knowledge |
| Memory | better-supabase/blocks/memory | memory |
| Agents | better-supabase/blocks/agents | agents |
| Connectors | better-supabase/blocks/connectors | connectors |
| AI tasks | better-supabase/blocks/ai-tasks | ai-tasks |
| Push notifications | better-supabase/blocks/push | push |
| AI cache | better-supabase/blocks/ai-cache | ai-cache |
| AI providers | better-supabase/blocks/ai-providers | ai-providers |
| Credentials | better-supabase/credentials, better-supabase/vercel-connect | credentials |
SDK adapters
An adapter connects a third-party SDK to the blocks above. It owns no tables and no SQL modules of its own: install the modules of the blocks it uses.
| Adapter | Subpath | Uses the SQL modules of |
|---|---|---|
| AI SDK | better-supabase/ai-sdk and its chat, files, cache, memory and other entries | ai-chat, ai-files, ai-cache, memory, knowledge, agents, connectors, ai-providers |
| Chat SDK | better-supabase/chat-sdk, better-supabase/chat-sdk/react | chat-sdk-state, inbox |
| Workflow SDK | better-supabase/workflow-sdk, better-supabase/workflow-sdk/world | workflow-sdk-world, workflows |
| eve | better-supabase/eve | workflow-sdk-world, ai-chat, memory, knowledge, credentials, inbox |
How modules work together
sql add installs a module with the modules it requires. The modules it
works with are optional: when one of them is installed too, the module calls
it (it queues a job, sends a notification, checks a plan), and without it the
module installs and runs without that step. Every module also writes its
events to the outbox when the outbox is installed.
better-supabase sql list prints both lists for each module.
| Module | Requires | Works with |
|---|---|---|
updated-at | none | none |
actor | none | none |
audit | none | access, organizations |
tenant | updated-at | access, invitations, organizations |
invitations | tenant, access, updated-at | organizations, profiles |
reserved-slugs | none | none |
jobs | none | none |
idempotency | none | none |
webhook-inbox | none | none |
realtime-tables | none | none |
jsonb-schemas | none | none |
pgtap | none | none |
grants | none | none |
read-sets | none | none |
mfa | none | none |
entitlements | tenant | none |
rate-limit | none | none |
vector-search | none | none |
access | tenant | invitations, organizations |
support-sessions | access, audit | invitations |
organizations | tenant, access, updated-at | audit, data-lifecycle, entitlements, invitations, reserved-slugs |
profiles | none | tenant |
outbox | none | none |
notifications | updated-at | access, profiles |
webhooks-out | updated-at | access |
sessions | none | none |
webhooks-in | access, updated-at, webhook-inbox | none |
api-keys | tenant, access | none |
settings | tenant, access | jsonb-schemas |
usage | tenant, access | entitlements |
billing | tenant, access | organizations |
flags | tenant | access, entitlements |
comments | tenant, access | jsonb-schemas, notifications |
attachments | tenant, access | none |
data-lifecycle | tenant, access | every installed module |
sso | tenant, access | organizations |
onboarding | tenant, access | outbox |
waitlist | tenant, access | invitations, organizations |
announcements | tenant, access | entitlements |
streams | none | none |
credentials | none | none |
workflows | tenant, access | none |
workflow-sdk-world | workflows, jobs, access | none |
workflow-builder | workflows, tenant, access | none |
chat-sdk-state | none | none |
inbox | tenant, access, updated-at | jobs, notifications |
ai-chat | tenant, access, streams | entitlements, ai-providers |
ai-files | tenant, access | ai-chat |
knowledge | tenant, access, vector-search | ai-chat, ai-files, jobs |
memory | tenant, access, vector-search | ai-chat |
agents | tenant, access | none |
connectors | tenant, access | none |
ai-tasks | tenant, access | agents, ai-chat, jobs |
push | none | none |
ai-cache | none | none |
ai-providers | tenant, access | ai-chat |
ensure-rls | none | none |
The other SQL modules (updated-at, actor, rate-limit,
support-sessions and the rest) are listed on the
SQL modules page. The
roadmap lists what comes next.
Last updated on