# Custom repositories

> Domain methods with defineRepository and extend, and the escape hatches below them.

Source: https://bettersupabase.com/docs/extending/repositories

## defineRepository [#definerepository]

Put domain queries next to the table they belong to. `defineRepository`
returns a plugin that adds methods to one table only:

```ts title="src/lib/supabase/index.ts"
import { defineRepository, defineSupabase } from "better-supabase";

const base = defineSupabase(schema).use(tenant()).use(softDelete());

const customers = defineRepository(base, "customers", (repo) => ({
  active: () =>
    repo.findMany({ where: { status: "active" }, orderBy: { name: "asc" } }),
  byKvk: (kvk: string) => repo.findFirst({ where: { kvk } }),
}));

export const betterSupabase = base.use(customers);
```

```ts
const rows = await ctx.db.customers.active().orThrow();
```

`repo` is the repository as your app sees it: every plugin (tenant filters,
soft delete, `restore()`) applies, and payload types flow through. The
methods are typed on `db.customers` and don't exist on other tables.

Each table can have one `defineRepository`; the plugin is named
`repository:<table>`, and redefining a built-in method such as `findMany`
throws when the repository is first used.

## extend [#extend]

For methods that only one module needs, extend a repository in place:

```ts
const customers = ctx.db.customers.extend((repo) => ({
  withOpenInvoices: () =>
    repo.findMany({ where: { invoices: { some: { status: "open" } } } }),
}));
```

`extend` returns a new object; `ctx.db.customers` is unchanged.

## Escape hatches [#escape-hatches]

Each layer has a way out when the repository doesn't cover a query:

| Need                                     | Use                                                                                       |
| ---------------------------------------- | ----------------------------------------------------------------------------------------- |
| A PostgREST feature the repository lacks | `db.$client`, the supabase-js client (database column names)                              |
| A database function                      | `db.$rpc(name, args, { returns })`                                                        |
| Raw SQL under RLS                        | `ctx.sql` from [`/postgres`](/docs/auth/postgres)                                         |
| A different backend                      | an [`Executor`](/docs/extending/interfaces#executor) passed to `betterSupabase.connect()` |
| Rewriting queries everywhere             | a [plugin](/docs/extending/plugins) with `transformQuery`                                 |