---
title: Collections and rules
description: Real tables, and rules compiled into the SQL that reads them.
section: Data
order: 1
---

# Collections and rules

<p class="lead">A collection is a real SQLite table. Its rules are compiled into the same query that fetches its rows, not checked afterwards.</p>

## Fields

The field types are `text`, `number`, `bool`, `email`, `url`, `date`, `json`, `select`, `relation` and `file`. A collection may declare composite and unique indexes. A collection that declares `"extends": "_users"` holds an app's own profile of a person, under the same id as their identity.

## Five rules

```json title="schema.json"
"rules": {
  "list":   "@request.auth.id != null",
  "view":   "@request.auth.id != null",
  "create": "author = @request.auth.id",
  "update": "author = @request.auth.id",
  "delete": "author = @request.auth.id || @request.auth.staff = true"
}
```

- With no rule, only superusers have access; `""` means anybody.
- A list rule filters: a caller gets only the rows they may see, and the counts stay correct.
- View, update and delete answer 404 rather than 403, so a rule can't be used to learn that a row exists.
- After a write, the row is checked again inside its transaction. A write that would move a row out of the writer's reach is rolled back.

Fields can have their own rules too, such as who may read a pupil's grade or change a status. They are enforced in the same SQL.

## The API

```http title="HTTP"
GET    /api/collections/todos/records?filter=done=false&sort=-created
POST   /api/collections/todos/records
PATCH  /api/collections/todos/records/:id
DELETE /api/collections/todos/records/:id

// server-sent events, each row read as you
GET    /api/realtime?subscribe=todos
```

```ts title="Browser"
import { Sluurp } from "sluurp";
const sluurp = new Sluurp();
await sluurp.collection("todos").create({ title: "Milk", author: sluurp.auth.id });
const open = await sluurp.collection("todos").list({ filter: "done = false" });
```

Filters are parsed and compiled with bound parameters. A filter that names a field that doesn't exist is refused before any SQL is built.

## Projects

Each project is its own database file, and no query spans two. An app is pinned to one project, so its functions can't reach another app's data.
