# Data and access rules

Define your tables in `flatstack/schema.ts`. Each table says who may read and write its rows, and Flatstack enforces that for your app's users.

```ts
import { integer, owner, table, text, timestamp } from "@flatstack/server/schema";

export const todos = table("todos", {
  id: text().primaryKey().$defaultFn(() => crypto.randomUUID()),
  userId: owner(),
  title: text().notNull(),
  done: integer({ mode: "boolean" }).default(false),
  createdAt: timestamp().defaultNow(),
});
```

Columns are Drizzle columns (`text`, `integer`, `real`, plus `timestamp()`, `owner()` and `ref(otherTable)`), so rows are fully typed.

## Access rules

| Rule | Who |
| --- | --- |
| `owner` | Signed-in users, only for rows whose `owner()` column is theirs. |
| `authenticated` | Any signed-in user. |
| `public` | Anyone, signed in or not. |
| `deny` | Nobody (server-only code can still use `asAdmin()`). |
| `(ctx, row) => boolean` | Whoever the function allows, checked row by row. |

```ts
export const posts = table(
  "posts",
  { id: text().primaryKey(), authorId: owner(), title: text().notNull() },
  { access: { read: "public", write: "owner" } },
);
```

Defaults, when you leave out `access`:

- A table with an `owner()` column is owner-only for reading and writing.
- A table without one is closed to your app's users.

`public` is never a default: you have to write it.

## Querying as the user

In a function, `ctx.db.asUser()` applies the rules for whoever is calling:

```ts
const db = ctx.db.asUser();
await db.insert(todos, { title: "Buy milk" }); // userId is set to the caller
await db.select(todos, { where: eq(todos.done, false), limit: 20 });
await db.update(todos, { done: true }, { where: eq(todos.id, id) });
await db.delete(todos, { where: eq(todos.id, id) });
```

- Owner tables: inserts set the owner column to the caller. Naming someone else as the owner, or moving a row to another owner, is refused with `403 forbidden`.
- Updates and deletes only touch rows the caller may write. Rows they can't write are left alone, and aren't returned.
- A rule that needs a signed-in user answers `401 unauthenticated` when nobody is signed in.
- With a function rule, reads are filtered after the query, so a page can come back with fewer rows than `limit`. Writes are all or nothing: one row the rule rejects refuses the whole write.

`asUser()` covers single-table queries. For joins or raw SQL, use `ctx.db.asAdmin()`, which ignores access rules. Use it only in server-only code.

## The data API

Add `expose: true` to serve a table at `/api/data/<table>` under these same rules. See [Data API](data-api.md).