# Configuration

> better-supabase.config.ts options and defaults.

Source: https://bettersupabase.com/docs/cli/config

The CLI looks for `better-supabase.config.{ts,mts,js,mjs,json}` in the
working directory, then in each parent directory up to the one that holds
`.git`, and loads the first it finds. Paths in the file (`output`, `sql.dir`
and the rest) are relative to the directory that holds it, so a config at the
repository root works when the CLI runs from a package. `--config` overrides
the search. TypeScript configs load natively on Node 24.

```ts title="better-supabase.config.ts"
import { defineConfig, zod } from "better-supabase/config";
import { CustomerMetadata } from "./src/types.ts";

export default defineConfig({
  source: { projectRef: "abcdefghijklmnopqrst" },
  schemas: ["public"],
  casing: "camel",
  output: "src/lib/supabase/generated.ts",
  codecs: { int8: "bigint", numeric: "string", timestamptz: "instant" },
  json: {
    "customers.metadata": {
      import: "./src/types.ts#CustomerMetadata",
      schema: CustomerMetadata,
    },
    "notes.attachments": {
      type: "{ files: { name: string; size: number }[] }",
    },
  },
  sensitive: ["contacts.email", "contacts.phone"],
  tables: {
    customers: { relations: { primaryContact: "contact" } },
    internal_jobs: { exclude: true },
  },
  plugins: {
    timestamps: true,
    softDelete: { column: "archived_at" },
    tenant: { column: "organization_id" },
    actor: true,
  },
  realtime: { tables: ["customers", "notes"] },
  generators: [zod()],
});
```

## Options [#options]

| Option             | Default                                                                                      | Notes                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------ | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source`           | local stack                                                                                  | `dbUrl`, `projectRef` (+ `accessToken`, default `$SUPABASE_ACCESS_TOKEN`), `snapshot` or `lite`. See [gen](/docs/cli/gen#where-the-schema-comes-from)                                                                                                                                                                                                                                              |
| `schemas`          | `['public']`                                                                                 | Tables in other schemas get prefixed keys                                                                                                                                                                                                                                                                                                                                                          |
| `casing`           | `'snake'`                                                                                    | `'camel'` maps columns in queries; see [casing](/docs/concepts/casing)                                                                                                                                                                                                                                                                                                                             |
| `tables`           | `{}`                                                                                         | Per-table `casing`, `exclude`, `serviceRole`, relation renames and `insertOptional`, keyed by table name or `schema.table`. `serviceRole` keeps the models but tells [doctor](/docs/cli/doctor#bs106) the table is for `service_role` only; `insertOptional` lists not-null columns the database fills on insert                                                                                   |
| `output`           | `src/lib/supabase/generated.ts`                                                              | `database.types.ts` and `generated.meta.js` are written next to it                                                                                                                                                                                                                                                                                                                                 |
| `postgrestVersion` | `'13'`                                                                                       | Written to `__InternalSupabase` in `database.types.ts`                                                                                                                                                                                                                                                                                                                                             |
| `codecs`           | JSON types                                                                                   | `int8: 'bigint' \| 'string'`, `numeric: 'string'`, `timestamptz: 'instant'` read values exactly; see [Temporal](/docs/concepts/temporal)                                                                                                                                                                                                                                                           |
| `relations`        | `{ nullableUnderRls: false }`                                                                | `nullableUnderRls: true` types to-one includes of tables with RLS as `\| null`; see [includes](/docs/repository/includes#row-level-security)                                                                                                                                                                                                                                                       |
| `json`             | `{}`                                                                                         | jsonb types, keyed by `table.column` or `schema.table.column` (database names). `schema` adds a [database check](/docs/blocks/sql#enforcing-jsonb-shapes)                                                                                                                                                                                                                                          |
| `sensitive`        | `[]`                                                                                         | Columns the [`noSensitiveSelect` rule](/docs/plugins/rules) guards (`table.column`)                                                                                                                                                                                                                                                                                                                |
| `generators`       | `[]`                                                                                         | `zod()`, `valibot()`, `jsonSchema()`, `standardSchema()` or your own                                                                                                                                                                                                                                                                                                                               |
| `claims`           | `{ tenant: 'tenant_id', scope: 'tenant', features: 'features', memberships: 'memberships' }` | JWT claim names. `tenant` is the active-tenant claim the tenant plugin, `current_tenant_id()` and the storage and realtime policies read; `scope` is the `scope` of the module's `memberships` entries; `features` is the plan-features claim. See [Claims](#claims)                                                                                                                               |
| `plugins`          | none                                                                                         | Flags for timestamps, soft delete, tenant and actor columns                                                                                                                                                                                                                                                                                                                                        |
| `buckets`          | `{}`                                                                                         | Typed storage buckets; doctor compares them with the database and `config.toml`                                                                                                                                                                                                                                                                                                                    |
| `storagePaths`     | `{}`                                                                                         | Text columns that hold object paths, keyed by `table.column`, with a `buckets` key or bucket id as the value. They're typed as [`StoragePath<'bucket-id'>`](/docs/platform/storage#path-columns)                                                                                                                                                                                                   |
| `functions`        | `{}`                                                                                         | Function results that are never null, keyed by function name or `schema.name`: `{ notNull: true }` or the `returns table` columns. See [gen](/docs/cli/gen)                                                                                                                                                                                                                                        |
| `topics`           | `{}`                                                                                         | Realtime topic templates                                                                                                                                                                                                                                                                                                                                                                           |
| `realtime`         | `{ tables: [], global: [], users: {} }`                                                      | Tables for [live queries](/docs/frontend/live-queries); `global` lists the ones without a tenant column, `users` maps per-user tables to their user column                                                                                                                                                                                                                                         |
| `entitlements`     | `{ key: 'id' }`; `customer` defaults to the organizations module's `stripe_customer_id`      | Where the [`entitlements` SQL module](/docs/blocks/entitlements) finds each tenant's Stripe customer, or `source` (`{ plans }` or `"custom"`) for entitlements without the Stripe Sync Engine. `memberships` (`"tenant"` or `"provider"`) picks where it reads memberships from                                                                                                                    |
| `authorization`    | none                                                                                         | An [authorization provider](/docs/extending/authorization-providers) the `provider` access model, the entitlements module, bucket and topic policies and doctor delegate to                                                                                                                                                                                                                        |
| `vectorSearch`     | `{}`                                                                                         | Embedding columns (`{ chunks: 'embedding' }`) the [`vector-search` SQL module](/docs/blocks/vector-search) writes `search_<table>` for                                                                                                                                                                                                                                                             |
| `readSets`         | `[]`                                                                                         | Modules exporting [read sets](/docs/repository/read-sets); `gen` compiles them into the `read-sets` SQL module                                                                                                                                                                                                                                                                                     |
| `sql`              | `supabase/schemas`, `900_better_supabase`, `supabase/tests`                                  | Where generated SQL and pgTAP files go. `modules` lists the [SQL modules](/docs/blocks/sql) to keep in sync: a list of names, or an object keyed by module name whose values set its mode, schema, table and column names, id type and permission keys ([existing tables](/docs/blocks/sql#existing-tables-managed-adopt-and-custom)). `access` also picks the [access model](/docs/blocks/access) |
| `seed`             | `supabase/seed.ts`, `supabase/seeds/000_better_supabase.sql`                                 | Entry module and output of [`seed`](/docs/cli/local#seed)                                                                                                                                                                                                                                                                                                                                          |
| `specs`            | `src/lib/openapi.ts`, one OpenAPI 3.1 output at `openapi.json`, `failOn: 'error'`            | Entry module, outputs (`format`, `version`, `output`, `overlays`, `serialize`, `ui`) failure level and `manifest` file of [`spec`](/docs/cli/spec#configure-the-outputs)                                                                                                                                                                                                                           |
| `openapi`          | `src/lib/openapi.ts`, `openapi.json`                                                         | Deprecated alias of `specs`. Entry module and output of [`openapi emit`](/docs/cli/local#openapi-emit); `spec` reads them when `specs` is unset                                                                                                                                                                                                                                                    |
| `doctor`           | `format: 'text'`                                                                             | `ignore` finding codes, `strict` mode, `claimsLimit`, `policyHelperLimit`, and the defaults of `--only`, `--format` and `--out` (`only`, `format`, `output`)                                                                                                                                                                                                                                       |
| `gen`              | every configured task, `watchInterval: 2000`                                                 | `tasks` [`gen`](/docs/cli/gen#tasks) runs (`types`, `sql`, `spec`, `seed`, `scaffold`, `env`) and the `--watch` interval, which `spec --watch` reads too                                                                                                                                                                                                                                           |
| `env`              | `output: '.env.local'`, prefix from the framework                                            | Defaults of [`env`](/docs/cli/local#env) `--out` and `--prefix` (`output`, `prefix`; `''` for no prefix)                                                                                                                                                                                                                                                                                           |
| `scaffold`         | none                                                                                         | `api`: the `framework`, `tables`, `name` and `basePath` of [`scaffold api`](/docs/cli/scaffold). Setting it adds the `scaffold` task to `gen`                                                                                                                                                                                                                                                      |
| `integrations`     | `[]`                                                                                         | The integrations `init` set up. [`add`](/docs/cli/init#add) with no argument sets up the ones that have no files yet                                                                                                                                                                                                                                                                               |
| `skills`           | detected agent folders                                                                       | `agents`: the folders [`skills install`](/docs/for-ai-agents) writes to (`cursor`, `claude`, `agents`)                                                                                                                                                                                                                                                                                             |
| `keys`             | `supabase/signing_keys.json`                                                                 | `output`: where [`keys`](/docs/cli/local#keys) writes the signing key                                                                                                                                                                                                                                                                                                                              |
| `$env`, `$ci`, ... | none                                                                                         | Overrides per [environment](#environments)                                                                                                                                                                                                                                                                                                                                                         |

## Flags and the config [#flags-and-the-config]

Every option a command takes on each run has a config key, so the command
line stays short and a fresh checkout behaves the same as yours. A value is
picked in this order:

1. the flag, for one run;
2. the active [environment](#environments) block;
3. the config;
4. the default.

| Command          | Config key                                      |
| ---------------- | ----------------------------------------------- |
| `gen`            | `gen.tasks`, `gen.watchInterval`                |
| `env`            | `env.output`, `env.prefix`                      |
| `doctor`         | `doctor.only`, `doctor.format`, `doctor.output` |
| `spec`           | `specs.manifest`, `gen.watchInterval`           |
| `scaffold api`   | `scaffold.api`                                  |
| `add`            | `integrations`                                  |
| `skills install` | `skills.agents`                                 |
| `keys`           | `keys.output`                                   |

`doctor.output` applies when the report uses `doctor.format`, so
`doctor --json` prints to stdout even when the config writes SARIF to a
file. `scaffold api` and `add` print the config entry for the flags you
passed, so you can paste it once instead of repeating them.

## Environments [#environments]

An environment block overrides part of the config for one environment. Name
blocks under `$env`, or use the `$development`, `$production`, `$test` and
`$ci` shorthands:

```ts title="better-supabase.config.ts"
export default defineConfig({
  casing: "camel",
  doctor: { ignore: ["BS204"] },
  $ci: {
    gen: { tasks: ["types", "sql", "spec"] },
    doctor: { format: "sarif", output: "doctor.sarif", strict: true },
  },
  $env: {
    staging: { source: { projectRef: "abcdefghijklmnopqrst" } },
  },
});
```

The CLI applies the block named by `--env`, then by `$BETTER_SUPABASE_ENV`,
and in CI (`$CI` is set) the `ci` block. When both `$env.<name>` and a
shorthand exist for the same name, the shorthand applies last. Objects merge
key by key, while lists and other values replace the base value: above, `ci`
keeps `doctor.ignore` and replaces `gen.tasks`. An environment without a
block uses the config as it is. A block can't hold another block.

## Claims [#claims]

The `claims` block names the claims every part of better-supabase reads, so a
rename happens in one place. The defaults are:

```ts title="better-supabase.config.ts"
export default defineConfig({
  claims: {
    tenant: "tenant_id", // the active tenant, also read from app_metadata
    scope: "tenant", // memberships[].scope written by the tenant SQL module
    features: "features", // plan features per tenant, from the entitlements module
    memberships: "memberships", // the caller's memberships, from the tenant module or a provider's hook
  },
});
```

`gen` writes the names that differ from the defaults into the generated
schema, and `sql add` and `sql sync` render them into the SQL modules. Run
both after changing the block.

An adapter that reads these claims itself takes the paths from `claimPaths`
in `better-supabase/config` instead of hardcoding them. `DEFAULT_CLAIMS` holds
the defaults:

```ts
import { claimPaths } from "better-supabase/config";

const paths = claimPaths(config.claims);
// { tenant: ["tenant_id", "app_metadata.tenant_id"], features: "features",
//   memberships: "memberships", scope: "tenant" }
```

## supabase/config.toml [#supabaseconfigtoml]

The CLI reads `supabase/config.toml` for the local database port, the auth
settings [doctor](/docs/cli/doctor) checks and bucket drift. When
[`@supabase/config`](https://www.npmjs.com/package/@supabase/config) is
installed, it parses the file, with `env()` values filled in the same way the
Supabase CLI does. It's an optional peer (it needs `effect` and
`@effect/platform-node`). Without it, a built-in parser reads the subset of
TOML that `supabase init` writes.

## JSON configs [#json-configs]

```json title="better-supabase.config.json"
{
  "$schema": "https://unpkg.com/better-supabase/schemas/config-v1.json",
  "casing": "camel",
  "plugins": { "timestamps": true }
}
```

The JSON Schema gives editor completion and validation. Generators and
Standard Schema values in `json[...].schema` need a TypeScript or JavaScript
config; a JSON config can use plain JSON Schema objects.

## Print the resolved config [#print-the-resolved-config]

```bash
pnpm better-supabase config
```

`config` prints the file it loaded and the active environment, then the
config with every default filled in and that environment's block merged in,
as JSON (`--json` wraps them in one document, with `environment` set to the
name or `null`). `better-supabase config --env ci` shows what CI will see. Generators show as
their name and `apiVersion`, and the password in `source.dbUrl` is redacted.
Use it to check which file the CLI found and what an option resolved to.

`gen` warns about `tables` and `json` keys that match nothing in the
configured schemas, such as a misspelled table name, instead of ignoring them.

## Custom generators [#custom-generators]

```ts
import type { Generator } from "better-supabase/config";

export const tableList: Generator = {
  apiVersion: 1,
  name: "table-list",
  generate: ({ model }) => [
    {
      path: "src/lib/tables.json",
      contents: JSON.stringify(
        model.tables.map((table) => ({
          key: table.key,
          columns: table.columns.map((column) => [column.app, column.tsType]),
        })),
      ),
    },
  ],
};
```

A generator receives the schema metadata (`meta`), the typegen introspection,
the resolved config, an `importPath` helper and `model`: the tables and enums
`gen` emitted, with the TypeScript type it wrote for each column after `json`
overrides, codecs and enum unions. `model` is frozen. The generator returns
files, and `gen --check` covers them too.

`apiVersion: 1` names the generator contract it targets. `gen` refuses a
generator that declares a version it doesn't know, so a generator written for
a later contract fails with a message instead of misreading its input.
Leaving it out means version 1.

`gen` records the files it wrote in
`node_modules/.cache/better-supabase/gen-manifest.json`. When a later run no
longer writes one of them (you removed a generator or changed `output`), it
deletes the file and lists it as removed, and `gen --check` reports it as no
longer generated. Files that no earlier run wrote are never touched. Prove a
generator with [`testGenerator`](/docs/extending/conformance).