# Security rules

> Row-level security for entities: the rule format, templates, user conditions and operators.


Rules live under `rls` in an entity schema, one per operation: `create`, `read`, `update`, `delete`.

## Rule values

| Value | Meaning |
|---|---|
| `true` / `false` | Everyone / no one (admins always can). |
| `{ "created_by": "{{user.email}}" }` | Only the record's creator. |
| `{ "data.team": "{{user.data.team}}" }` | Record field must match a field on the user. People can't set a field a rule reads for themselves: set it from a backend function with `users.update`. |
| `{ "user_condition": { "role": "admin" } }` | Only users matching the condition. |
| `{ "$or": [ ruleA, ruleB ] }` | Either rule (also `$and`, `$nor`). |

Record fields are addressed as `data.<field>`; the creator is `created_by`. Templates: `{{user.email}}`, `{{user.id}}`, `{{user.role}}`, `{{user.data.<field>}}`. A rule that references the user denies signed-out visitors. Field operators `$in`, `$nin`, `$ne` and `$all` work in rules too.

## Defaults

Without `rls`: create and read are open; update and delete require the creator. Anonymous records can then only be changed by admins.

## Who bypasses rules

- App users with the **admin** role.
- [Backend functions](/docs/developers/backend/functions) using the service client.
- You, in the dashboard.

## Patterns

```json
// Private to each user
"rls": { "create": true, "read": { "created_by": "{{user.email}}" }, "update": { "created_by": "{{user.email}}" }, "delete": { "created_by": "{{user.email}}" } }

// Public form, admin-only inbox
"rls": { "create": true, "read": { "user_condition": { "role": "admin" } }, "update": false, "delete": { "user_condition": { "role": "admin" } } }

// Published or own drafts
"rls": { "read": { "$or": [ { "data.published": true }, { "created_by": "{{user.email}}" } ] } }
```

## Field-level rules

`fls` maps a field name to optional `read` and `write` rules, written the same way as `rls`. A field that fails its `read` rule is removed from every response (list, filter, get, create and update results) and is ignored by filters. Setting a field that fails its `write` rule returns `403`. Admins and the service key always pass. The system field `created_by` can be protected too.

```json
"fls": {
  "phone": { "read": { "created_by": "{{user.email}}" } },
  "approved": { "write": { "user_condition": { "role": "admin" } } }
}
```
