# Presets

> Start an app from a named preset or a code from /create, with every block it needs and the conventions it sets.

Source: https://bettersupabase.com/docs/presets

A preset is a block selection plus the conventions to install it with:
framework, platforms, casing, id type, scope, tenant name, access model, API
schema and integrations. Put a named preset or a code from
[Create](https://bettersupabase.com/create) in your config, and
`defineConfig` expands it into `blocks`, with the blocks they require:

```ts title="better-supabase.config.ts"
import { defineConfig } from "better-supabase/config";

export default defineConfig({
  preset: "saas",
  sql: { api: "api" },
  blocks: {
    comments: { options: { maxBodyLength: 5000 } },
    tags: {},
  },
});
```

The keys you set win. A block you name joins the preset, and its entry is
merged over the preset's settings for that block. `casing`, `sql.api` and
`integrations` from the preset apply only when the config leaves them unset.
`npx better-supabase sql sync` then writes the files of every block the
preset expands to, and `supabase db schema declarative sync` turns them into
a migration ([SQL modules](/docs/blocks/sql)).

A new project starts from a preset with
`npx better-supabase init --preset saas`, which writes the config, the module
files and `lib/blocks.ts` in one step. `better-supabase apply --preset crm`
applies one to a project you have, and `better-supabase preset info` shows
how your config differs from its preset ([CLI](/docs/cli/presets)).

## Named presets [#named-presets]

| Preset       | Blocks                                                                                                                           |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `minimal`    | profiles                                                                                                                         |
| `saas`       | organizations, invitations, teams, profiles, billing, entitlements, notifications, audit, api-keys, settings, onboarding, outbox |
| `crm`        | `saas` plus parties, pipelines, engagements, tags, comments, custom-fields, saved-views, search                                  |
| `projects`   | `saas` plus tags, comments, attachments, state-machine, rank, reminders, time-tracking                                           |
| `commerce`   | `saas` plus catalog, inventory, orders, promotions                                                                               |
| `scheduling` | `saas` plus calendar, bookings, reminders                                                                                        |
| `ai`         | `saas` plus ai-chat, ai-files, knowledge, memory, agents, ai-tasks                                                               |
| `everything` | every block, for demos and tests                                                                                                 |

Each one also installs what its blocks require (`tenant`, `access`,
`updated-at`, ...). The named presets use the default conventions.
`tests/validation-presets` renders each one, applies it to the local stack
as one migration, and typechecks the `lib/blocks.ts` it generates.

## Apps built on a preset [#apps-built-on-a-preset]

These pages start from a preset and add the subject table, the options and
the event flow of one product. `tests/validation-recipes` installs each
config on the local stack and drives the flows end to end.

| App                                    | Preset       | Subject    | Adds                                                               |
| -------------------------------------- | ------------ | ---------- | ------------------------------------------------------------------ |
| [CRM](/docs/presets/crm)               | `crm`        | `deal`     | rank, reminders, state-machine                                     |
| [Projects](/docs/presets/projects)     | `projects`   | `task`     | number-sequences, the state machine's workflow                     |
| [Drive](/docs/presets/drive)           | `saas`       | `document` | comments, tags, attachments, share-links, rank                     |
| [Scheduling](/docs/presets/scheduling) | `scheduling` | `booking`  | custom-fields, share-links, state-machine                          |
| [Commerce](/docs/presets/commerce)     | `commerce`   | `order`    | imports, number-sequences, search, saved-views, metrics, approvals |

## Codes and Create [#codes-and-create]

[Create](https://bettersupabase.com/create) builds a preset in the browser
and keeps it in the URL as `/create?preset=<code>`. The code also works as
`preset` in the config, with `init --preset` and with `apply --preset`, and
so does the whole URL. See
[Codes and the resolver](/docs/presets/codes) for the encoding and the
`better-supabase/preset` API.

## The shared wiring [#the-shared-wiring]

Three pieces connect the blocks in every recipe.

The subject is declared once in `config.subjects`. Comments, tags, custom
fields, share links and reminders all read it from the
[subject registry](/docs/blocks/sql#subject-registry), and `rank` reads its `rank` entry.

A state machine is named after its subject (`deal`, `task`, `booking`). The
machine name is the `subjectType` of its history rows and of
`status.changed`, so a status change reaches the same watchers as a comment.

A cron route hands the outbox to the watchers. `watcherHandler` reads the
actor from each outbox row, so whoever acts is not notified of their own
change:

```ts title="app/api/cron/watchers/route.ts"
import { watcherHandler } from "better-supabase/blocks/notifications";

const watchers = watcherHandler(notifications, {
  types: {
    "comment.created": "subject.commented",
    "subject.tagged": "subject.tagged",
    "status.changed": "subject.moved",
    "share_link.created": "subject.shared",
    "field_value.set": "subject.field_changed",
  },
});

export const GET = outbox.relayRoute({
  secret: process.env.CRON_SECRET,
  consumers: { watchers },
});
```

Users follow a row with `notifications.subscribe({ subject, level })`.
Watchers at level `all` get every mapped event. See
[watchers](/docs/blocks/notifications#watchers) for rules with `activity`,
`recipients` and `summary`.

Reminders deliver through the `notifications` block, the `notifier` port,
when the reminders block has a `notification` type. The reminder's owner and the subject's watchers get
one notification per occurrence:

```ts title="lib/blocks.ts"
const { comments, reminders, stateMachine } = createBlocks(
  { transport, service },
  {
    notifications: notificationsBlock.using((options) =>
      createNotifications({ ...options, types }),
    ),
    comments: commentsBlock,
    reminders: remindersBlock.with({ notification: "reminder.due" }),
    stateMachine: stateMachineBlock,
  },
);
```

`reminders.job` runs as the handler of the `reminders.deliver` queue
([delivery](/docs/blocks/reminders#delivery)).