> ## 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.

# Roles & row-level access

> Restrict who can read, create, update, or delete which records with roles and row filters in zite.permissions.json.

Declare roles and row filters once in `zite.permissions.json` at the workspace root; every app and [workflow](/framework/workflows) enforces them on every `zite.*` call. Schema → [permissions file](/framework/permissions-file); evaluation model → [Roles & permissions](/concepts/permissions).

## A practical file

Managers get full access to `Deals`; account executives can read and update only their own rows:

```json theme={"system"}
{
  "version": "1.0",
  "defaultPolicy": "deny",
  "roles": [
    { "id": "sales-manager", "name": "Sales manager" },
    { "id": "account-executive", "name": "Account executive" }
  ],
  "tables": {
    "Deals": {
      "rules": [
        { "roleId": "sales-manager", "operation": "read" },
        { "roleId": "sales-manager", "operation": "create" },
        { "roleId": "sales-manager", "operation": "update" },
        { "roleId": "sales-manager", "operation": "delete" },
        {
          "roleId": "account-executive",
          "operation": "read",
          "rowFilter": {
            "type": "comparison",
            "field": "ownerEmail",
            "operator": "eq",
            "userField": "email"
          }
        },
        {
          "roleId": "account-executive",
          "operation": "update",
          "rowFilter": {
            "type": "comparison",
            "field": "ownerEmail",
            "operator": "eq",
            "userField": "email"
          }
        }
      ]
    }
  }
}
```

One rule covers one role and one operation, so granting four operations means four rules. Use the built-in
`builtin:all-team-members` role id to grant something to everyone internal.

## Gotchas

* **`"defaultPolicy": "deny"` is what turns roles on.** Leave it out and the whole file is inert: every rule above is stored and none is enforced, with no error. This is the single most common way a rule set silently does nothing.
* **Table keys are PascalCase.** `"Deals"`, not `"deals"`, even though you call it as `zite.deals`. A mis-cased key passes validation and then matches nothing, which under deny-by-default locks the table rather than opening it.
* **Rules cover external apps too.** External and anonymous visitors match `builtin:all-users`, not `builtin:all-team-members`, so a file that names only the team-members built-in locks every public visitor out. Scope a public app's data with a row filter on a `userField`, and require a session on the workflow:

  ```typescript theme={"system"}
  // in a workflow of an external app
  export default createEndpoint({
    authenticated: true, // without this, context.user is null for anonymous visitors
    execute: async ({ context }) =>
      zite.deals.findAll({ filters: { ownerEmail: context.user.email } }),
  });
  ```

  See [Auth](/framework/auth) and [Authentication](/concepts/authentication).
* **Check filter keys against `.zite/db.ts`.** An unrecognized key in `filters` is dropped rather than raising, so a typo makes the query broader than you wrote. See [the database client](/framework/database-client#filters-and-sorting).
* **`rowFilter` does not apply to `create`.** A create rule grants the operation outright; enforce constraints on new rows in the workflow.
* **You define roles here; a human assigns them** on the **Members** tab. Keep each role's `id` stable across renames so assignments survive.
* **Administrators and workspace editors bypass all rules** when using internal apps.


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