# Lint rules

> Catch unbounded reads and unscoped deletes in the editor, with ESLint or oxlint.

Source: https://bettersupabase.com/docs/plugins/lint

`better-supabase/lint` is a lint plugin written against the ESLint rule API.
It has no dependencies and loads in ESLint's flat config and in oxlint's JS
plugins.

## ESLint [#eslint]

```js title="eslint.config.js"
import betterSupabase from "better-supabase/lint";

export default [betterSupabase.configs.recommended];
```

## oxlint [#oxlint]

```json title=".oxlintrc.json"
{
  "jsPlugins": ["better-supabase/lint"],
  "rules": {
    "better-supabase/no-delete-many-without-where": "error",
    "better-supabase/no-unbounded-find-many": "warn",
    "better-supabase/max-limit": ["warn", { "max": 500 }],
    "better-supabase/require-order-by": "warn",
    "better-supabase/unbounded-read": ["warn", { "tables": ["public.events"] }]
  }
}
```

## Rules [#rules]

### no-unbounded-find-many [#no-unbounded-find-many]

`findMany()` or `findMany({ ... })` without `limit`. Recommended: `warn`.

### no-delete-many-without-where [#no-delete-many-without-where]

`deleteMany()` without `where`. The repository refuses this at runtime too;
the rule catches it before it ships. Recommended: `error`.

### max-limit [#max-limit]

A literal `limit` above `max` (default 1000) in `findMany`, `findFirst` or
`paginate`. Recommended: `warn`.

### require-order-by [#require-order-by]

`findMany` with `offset` but no `orderBy`, which returns pages in no stable
order. Recommended: `warn`.

### require-max-affected [#require-max-affected]

`updateMany` or `deleteMany` without
[`maxAffected`](/docs/repository/writing#limiting-bulk-writes), or with a
literal `maxAffected` above `max` (default 1000). Not in `recommended`; turn
it on where a bulk write that matches too many rows would hurt:

```js title="eslint.config.js"
export default [
  betterSupabase.configs.recommended,
  {
    rules: { "better-supabase/require-max-affected": ["error", { max: 500 }] },
  },
];
```

### unbounded-read [#unbounded-read]

`db.<table>.findMany` without `limit` on a large table. PostgREST cuts such a
read off at `db-max-rows` without an error (see
[row caps](/docs/repository/pagination#row-caps)). Recommended: `warn`, but
it only reports once it knows which tables are large:

* `tables`: the large tables. `largeTables(snapshot)` reads them from
  `supabase/snapshot.json`, where `gen` marks every table Postgres estimates
  at 10,000 rows or more. Re-run `gen` against a database with realistic
  data (or production) to update the marks.
* `strict: true`: report on every table, like `no-unbounded-find-many`.

```js title="eslint.config.js"
import betterSupabase, { largeTables } from "better-supabase/lint";
import snapshot from "./supabase/snapshot.json" with { type: "json" };

export default [
  betterSupabase.configs.recommended,
  {
    rules: {
      "better-supabase/unbounded-read": [
        "warn",
        { tables: largeTables(snapshot) },
      ],
    },
  },
];
```

Table names match regardless of casing: `order_items`, `orderItems` and
`public.order_items` are the same table.

## How calls are matched [#how-calls-are-matched]

Rules match calls by method name with an inline object literal. When the
argument is a variable or contains a spread, the rule skips it instead of
guessing, so there are no false positives from code it can't see. The
runtime [`rules()` plugin](/docs/plugins/rules) checks what the linter
can't.