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

# Writing Permission Rules

> Learn how to write and use permission rules in SnackBase's rule engine for fine-grained access control

SnackBase includes a **powerful rule engine** that compiles expression-based rules into efficient SQL WHERE clauses. This guide explains the syntax, variables, and best practices for writing rules.

## Overview

Rules are **boolean expressions** associated with collection operations (`list`, `view`, `create`, `update`, `delete`).

```python theme={null}
# Rule example: Users can only see their own records
created_by = @request.auth.id
```

### When to Use Rules

| Scenario                | Example                                            |   |                                  |
| :---------------------- | :------------------------------------------------- | - | -------------------------------- |
| **Record ownership**    | `created_by = @request.auth.id`                    |   |                                  |
| **Role-based access**   | `@request.auth.role = "admin"`                     |   |                                  |
| **Status-based access** | \`status = "published"                             |   | @request.auth.role = "admin"\`   |
| **Pattern matching**    | `email ~ "%@company.com"`                          |   |                                  |
| **Complex logic**       | \`(priority > 5 && @request.auth.role = "manager") |   | created\_by = @request.auth.id\` |

## Rule Syntax

### Basic Expressions

```python theme={null}
# Field comparisons
@request.auth.id = "user_abc123"
@request.auth.email = "admin@example.com"
status = "published"

# Numeric comparisons
views > 1000
priority >= 3
quantity <= 50

# Pattern matching (SQL LIKE)
title ~ "Draft%"  # Starts with Draft
email ~ "%@gmail.com"  # Ends with @gmail.com

# Negation
!is_archived
status != "archived"
```

### Logical Operators

| Operator | Usage            | Description                  |             |                         |         |                                     |
| :------- | :--------------- | :--------------------------- | ----------- | ----------------------- | ------- | ----------------------------------- |
| `&&`     | `cond1 && cond2` | Both conditions must be true |             |                         |         |                                     |
| \`       |                  | \`                           | \`cond1     |                         | cond1\` | At least one condition must be true |
| `!`      | `!condition`     | Simple negation              |             |                         |         |                                     |
| `()`     | \`(c1            |                              | c2) && c3\` | Grouping for precedence |         |                                     |

**Operator Precedence**: `()` > `!` > comparisons > `&&` > `||`

## Evaluation Context

Rules have access to the following variables:

### 1. `@request.auth` (Standard)

| Field                      | Description             |
| :------------------------- | :---------------------- |
| `@request.auth.id`         | Current user ID         |
| `@request.auth.email`      | Current user email      |
| `@request.auth.role`       | Current user role       |
| `@request.auth.account_id` | Current account context |

### 2. `@request.data` (Action-specific)

Available during **create** and **update** operations.

* `@request.data.fieldname`: Accesses the value being sent in the request body.

Example: `!@request.data.is_admin` (Don't allow setting admin flag via this route).

### 3. Record Fields (Direct Access)

You can reference any field in the record directly by name.

* `created_by`
* `status`
* `your_custom_field`

## Built-in Macros

| Macro               | Description             | Example                  |
| :------------------ | :---------------------- | :----------------------- |
| `@has_role(role)`   | User has specific role  | `@has_role("admin")`     |
| `@has_group(group)` | User has specific group | `@has_group("managers")` |
| `@owns_record()`    | User is the creator     | `@owns_record()`         |
| `@is_creator()`     | Alias for @owns\_record | `@is_creator()`          |

## Rule Examples

### Example 1: Edit Own Drafts

Only allow editing if the user is the creator AND the status is "draft".

```python theme={null}
@owns_record() && status = "draft"
```

### Example 2: Admin or Owner

```python theme={null}
@request.auth.role = "admin" || @owns_record()
```

### Example 3: String Pattern Matching

Allow viewing only if the record type starts with "public".

```python theme={null}
type ~ "public%"
```

### Example 4: Complex Multi-Condition

```python theme={null}
# User can edit if:
# - They're an admin, OR
# - They own it AND it's not locked
@request.auth.role = "admin" || (@owns_record() && !is_locked)
```

## Testing Rules

### Manual Testing in Admin UI

SnackBase provides a built-in **Rule Tester** in the Collections settings:

1. Navigate to **Collections** -> **Edit Collection** -> **Rules**.
2. Write your rule.
3. Use the "Test Rule" button to simulate evaluation against a mock user and record.

### API Testing

You can also test rules via the API:

```bash theme={null}
POST /api/v1/roles/test-rule
{
  "rule": "@owns_record() && status = 'draft'",
  "user_id": "usr_123",
  "record": {"created_by": "usr_123", "status": "draft"}
}
```

## Best Practices

1. **Keep Rules Simple**: Complex logic is hard to debug. Use **SQL Macros** for complex multi-table checks.
2. **Use Parentheses**: Always group conditions in complex expressions to ensure correct evaluation order.
3. **Optimistic Rules**: In SnackBase, rules are compiled to `WHERE` clauses, so they are extremely fast.
4. **Deny by Default**: If no rule is defined for an operation, access is denied.

## Related Guides

* [Permissions Concept](/permissions)
* [SQL Macros Reference](../macros)
* [API Examples](../api-reference/examples)
