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

# Permissions

> The complete zite.permissions.json schema: roles, rules, and row filters.

Roles and data-access rules for a workspace live in a `zite.permissions.json` file at the workspace root.
This is the schema reference; for the concepts and evaluation model, see [Roles & permissions](/concepts/permissions).

## Structure

```typescript theme={"system"}
type ZitePermissionsJson = {
  version: '1.0';
  defaultPolicy?: 'allow' | 'deny';        // master switch: only 'deny' turns roles on
  roles: { id: string; name: string; description?: string }[];
  tables: Record<string, { rules: Rule[] }>; // keyed by the PascalCase table SDK name
};

type Rule = {
  roleId: string;                                      // a role id, never a display name
  operation: 'read' | 'create' | 'update' | 'delete';  // exactly one operation per rule
  rowFilter?: RowFilter;                               // limits which rows the rule covers
};

type RowFilter =
  | {
      type: 'comparison';
      field: string;                                   // a field on the table
      operator: 'eq' | 'neq' | 'contains' | 'gt' | 'gte' | 'lt' | 'lte';
      userField?: string;                              // dynamic value from context.user
      staticValue?: string | number | boolean;         // OR a fixed value (exactly one of the two)
    }
  | { type: 'and'; filters: RowFilter[] }
  | { type: 'or'; filters: RowFilter[] }
  | { type: 'not'; filter: RowFilter };
```

<Warning>
  **`defaultPolicy` is the master switch for roles, not a per-table default.** Setting it to `"deny"`
  turns roles on: every rule is enforced, and any table without an allow rule is blocked. Any other
  value, including omitting the field or setting `"allow"`, turns roles **off** and makes the entire
  file inert. The rules stay on disk, nothing is enforced, and there is no error to tell you.
</Warning>

## Fields

* **`version`**: always `"1.0"`.
* **`defaultPolicy`**: `"deny"` to enforce the file. See the warning above.
* **`roles`**: your custom roles, each with a stable `id` and a display `name`. Rules reference roles by
  **`id`**, so renaming a role never invalidates a rule or a member's assignment. Do not list the
  built-in roles here (see below).
* **`tables`**: keyed by the table's **PascalCase** SDK name, the string the generated client passes to
  `createTableClient` in `.zite/db.ts` (`"Orders"`, not `"orders"`). Each entry has a `rules` array.

<Warning>
  The permissions key is **not** the camelCase accessor you call in code. `zite.orders` is addressed as
  `"Orders"` here. A mis-cased key is still valid JSON and still passes validation, but it matches no
  table at runtime, so under `"defaultPolicy": "deny"` it silently grants nothing. See
  [SDK names](/concepts/databases#sdk-names).
</Warning>

## Built-in roles

Two roles exist on every workspace. They are **virtual**: reference their ids in rules, but never list
them in the `roles` array. An entry carrying a built-in id and its exact name is stripped when the file
is read, and a built-in id under any other name is rejected outright as a squat.

| Id | Name | Matches |
| - | - | - |
| `builtin:all-users` | All users | Everyone who can reach the app: internal members, external users, and anonymous visitors on endpoints without `authenticated: true` |
| `builtin:all-team-members` | All team members | Internal organization members only. External app users do **not** match this |

`builtin:all-users` does not mean "signed in". To require a session, set `authenticated: true` on the
workflow or add a `rowFilter` on a `userField`.

## Rules

Each rule is one role, one operation, and one optional filter. To grant a role several operations, write
one rule per operation.

* **`roleId`**: the id of a custom role, or a built-in id.
* **`operation`**: one of `read`, `create`, `update`, `delete`.
* **`rowFilter`**: restricts which rows the rule covers (like a Postgres `USING` clause).

Every rule is permissive. There are no deny rules: access is the union of the rules that match, and an
operation with no matching rule is denied.

<Note>
  `rowFilter` does not apply to `create`. A create rule grants the operation outright, so enforce any
  constraint on new rows in your workflow.
</Note>

## Row filters

A comparison filter compares a record `field` to either a value from the signed-in user (`userField`) or a
fixed `staticValue` (**exactly one** of the two) using `eq`, `neq`, `contains`, `gt`, `gte`, `lt`, or
`lte`. Compose comparisons with `and`, `or`, and `not`.

`userField` can reference the base user fields (`id`, `email`, `firstName`, `lastName`) plus any fields
synced from your [users table](/concepts/authentication#user-sync).

## Example

```json theme={"system"}
{
  "version": "1.0",
  "defaultPolicy": "deny",
  "roles": [
    { "id": "role_manager", "name": "Manager" }
  ],
  "tables": {
    "Orders": {
      "rules": [
        {
          "roleId": "builtin:all-team-members",
          "operation": "read",
          "rowFilter": { "type": "comparison", "field": "ownerEmail", "operator": "eq", "userField": "email" }
        },
        { "roleId": "role_manager", "operation": "read" },
        { "roleId": "role_manager", "operation": "update" },
        { "roleId": "role_manager", "operation": "delete" }
      ]
    }
  }
}
```

Here every team member can read only the orders they own, while Managers can read, update, and delete all
orders. Because `defaultPolicy` is `deny`, any table without rules is fully locked down.

<Note>
  Permissions are **table- and row-level** and govern the workspace database. There are no field-level
  permissions. Rules apply to internal and external apps alike. See
  [Roles & permissions](/concepts/permissions).
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.