> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Access Rules

> Attach read and write rules to your tables and let Bijection enforce them on every function

<Warning>
  Read rules, disclosure rules and the access model are in beta.
</Warning>

[Authentication](/auth/overview) tells your backend who is calling. The common
way to decide what that caller may do is to check it at the beginning of each
public function, as shown in [Auth in Functions](/auth/functions-auth). That
works, but the check lives in the function: a new query that forgets it, or an
internal function called from somewhere unexpected, reads the data anyway.

Access rules attach the decision to the table instead. You write each rule as
an ordinary query, name it in your [schema](/database/schemas), and Bijection
runs it for every read and every write of that table, whichever function makes
it.

This schema protects a `tasks` table so that only members of a task's project
can read it:

```ts bijection/schema.ts {12-15} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  defineSchema,
  defineTable,
  makeFunctionReference,
} from "bijection/server";
import { v } from "bijection/values";

export default defineSchema({
  memberships: defineTable({ project: v.string(), member: v.string() })
    .index("by_project_member", ["project", "member"]),
  tasks: defineTable({ project: v.string(), title: v.string() })
    .index("by_project", ["project"])
    .access({
      read: makeFunctionReference<"query">("access:taskRead"),
      reads: ["memberships", "tasks"],
    }),
});
```

The rule is a query in your `bijection/` directory. Bijection hands it the reads
it has to decide, and it answers each one:

```ts bijection/access.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  decideEach,
  readAccessArgs,
  readAccessResults,
} from "bijection/server";
import { internalQuery, type QueryCtx } from "./_generated/server";

// The project of one task, read by ID.
async function projectOf(ctx: QueryCtx, taskId: string) {
  const id = ctx.db.normalizeId("tasks", taskId);
  const task = id === null ? null : await ctx.db.get("tasks", id);
  return task?.project;
}

export const taskRead = internalQuery({
  args: readAccessArgs,
  returns: readAccessResults,
  handler: async (ctx, { requests }) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) throw new Error("Unauthenticated");
    return await decideEach(requests, async (request) => {
      // A read by ID names the task; an index range must pin one project.
      const project =
        request.target.kind === "object"
          ? await projectOf(ctx, request.target.id)
          : request.target.equalities.find((e) => e.field === "project")
              ?.value;
      if (typeof project !== "string") throw new Error("Pin a project");
      const membership = await ctx.db
        .query("memberships")
        .withIndex("by_project_member", (q) =>
          q.eq("project", project).eq("member", identity.tokenIdentifier),
        )
        .unique();
      if (membership === null) throw new Error("Not a member");
      return null;
    });
  },
});
```

Now every function that reads `tasks`, public or internal, query or mutation,
is checked against `taskRead`. A member listing their project's tasks gets
them; anyone else gets an error.

## What a rule decides

A table can carry three rules. Each one is an ordinary query you export.

| Rule | Declared with | Decides |
| - | - | - |
| [Read rule](/access/read-rules) | `.access({ read, reads })` | Which rows and properties of the table a caller may read |
| [Disclosure rule](/access/disclosure) | `.access({ disclose })` | Where data read from the table may be copied: other tables, HTTP requests, commands |
| [Write rule](/access/write-rules) | `.govern(...)` | Whether the changes a transaction makes to the table may commit |

A table without rules behaves exactly as before: any function can read and
write it.

## How rules differ from checks in functions

* **Rules run as the caller.** Inside a rule, `ctx.auth.getUserIdentity()`
  returns the identity of whoever called the function that is reading or
  writing. Rules build on [authentication](/auth/overview); they don't replace
  it.
* **Rules read private inputs.** The tables a rule lists in `reads` are open to
  the rule even when the caller can't read them. A membership table can decide
  access without being readable itself.
* **A refusal is an error, not a filter.** When a rule refuses, the function
  fails with `Read access was refused`, and nothing it computed from the
  refused data is returned. The one exception is an index range in a query,
  which leaves out the individual rows the rule refuses. See
  [What callers see](/access/read-rules#what-callers-see).
* **Rules stay reactive.** The rows a rule reads are dependencies of the query
  it decided, so granting or revoking a membership reruns the subscriptions it
  affects.

You can keep checking identity at the top of your functions: an early check
gives a clearer error than a refusal. The rule is what enforces the decision.

<Note>
  Rules decide reads made by your functions. Deployment administration is
  separate: snapshot exports, streaming exports and function logs are available
  to holders of the deployment's administrative permissions and are not
  filtered by your rules.
</Note>

## Declaring access once

Writing a rule per table by hand is repetitive once you have roles, teams and
nested objects. The [access model](/access/access-model) lets you declare
types, permissions, roles and grants once and generates the read, disclosure
and write rules for every table it covers.

Access to business [operations](/operations/overview) and approvals uses
separate permissions, which you manage with the
[`bijection permissions`](/access/permissions-cli) command.

<CardGroup cols={2}>
  <Card title="Read Rules" href="/access/read-rules">
    Decide which rows and properties each caller can read.
  </Card>

  <Card title="Disclosure Rules" href="/access/disclosure">
    Decide where protected data may be copied.
  </Card>

  <Card title="Write Rules" href="/access/write-rules">
    Authorize every change before it commits.
  </Card>

  <Card title="Access Model" href="/access/access-model">
    Roles, grants and restrictions that generate your rules.
  </Card>
</CardGroup>
