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

# API rules and access control

> Configure collection-level access control rules in PocketBase

PocketBase uses rule-based access control to determine who can access and modify your data. Rules are written as filter expressions and applied at the collection level.

## Rule types

Every collection (base, auth, or view) can have the following API rules:

| Rule            | Description                               | Available On              |
| --------------- | ----------------------------------------- | ------------------------- |
| **List Rule**   | Controls who can list/search records      | All collections           |
| **View Rule**   | Controls who can view individual records  | All collections           |
| **Create Rule** | Controls who can create records           | Base and auth collections |
| **Update Rule** | Controls who can update records           | Base and auth collections |
| **Delete Rule** | Controls who can delete records           | Base and auth collections |
| **Auth Rule**   | Additional constraints for authentication | Auth collections only     |
| **Manage Rule** | Admin-like permissions for auth records   | Auth collections only     |

<Note>
  Rules are evaluated using filter expressions, similar to the `filter` query parameter syntax.
</Note>

## Rule values

Each rule can have one of three values:

<Tabs>
  <Tab title="null (locked)">
    ```go theme={null}
    collection.ListRule = nil
    ```

    **Effect**: Only superusers can perform this action. Regular users and guests are denied access.

    <Warning>
      Use `nil` to completely lock down an action. This is the most restrictive setting.
    </Warning>
  </Tab>

  <Tab title="Empty string (public)">
    ```go theme={null}
    collection.ListRule = types.Pointer("")
    ```

    **Effect**: Anyone can perform this action, including unauthenticated guests.

    <Info>
      Use an empty string for public access. Be careful with sensitive data.
    </Info>
  </Tab>

  <Tab title="Filter expression">
    ```go theme={null}
    collection.ListRule = types.Pointer("@request.auth.id != ''")
    ```

    **Effect**: Only authenticated users matching the filter can perform this action.

    <Note>
      Use filter expressions for fine-grained access control.
    </Note>
  </Tab>
</Tabs>

## Auth rule

The `authRule` is special because it applies **after** successful authentication:

```go theme={null}
type collectionAuthOptions struct {
    // AuthRule could be used to specify additional record constraints
    // applied after record authentication and right before returning the
    // auth token response to the client.
    //
    // For example, to allow only verified users you could set it to
    // "verified = true".
    //
    // Set it to empty string to allow any Auth collection record to authenticate.
    //
    // Set it to nil to disallow authentication altogether for the collection
    // (that includes password, OAuth2, etc.).
    AuthRule *string `json:"authRule"`
}
```

**Common use cases:**

<CodeGroup>
  ```go Allow only verified users theme={null}
  collection.AuthRule = types.Pointer("verified = true")
  ```

  ```go Allow any authenticated user theme={null}
  collection.AuthRule = types.Pointer("")
  ```

  ```go Disable authentication completely theme={null}
  collection.AuthRule = nil
  ```

  ```go Allow only active users theme={null}
  collection.AuthRule = types.Pointer("status = 'active'")
  ```

  ```go Allow users with specific role theme={null}
  collection.AuthRule = types.Pointer("role = 'member' || role = 'admin'")
  ```
</CodeGroup>

The auth rule check happens in `RecordAuthResponse`:

```go theme={null}
// From apis/record_helpers.go:51
ok, err := e.App.CanAccessRecord(authRecord, originalRequestInfo, authRecord.Collection().AuthRule)
if !ok {
    return firstApiError(err, e.ForbiddenError(
        "The request doesn't satisfy the collection requirements to authenticate.", err))
}
```

## Manage rule

The `manageRule` gives admin-like permissions for auth records:

```go theme={null}
type collectionAuthOptions struct {
    // ManageRule gives admin-like permissions to allow fully managing
    // the auth record(s), eg. changing the password without requiring
    // to enter the old one, directly updating the verified state and email, etc.
    //
    // This rule is executed in addition to the Create and Update API rules.
    ManageRule *string `json:"manageRule"`
}
```

**What manage rule allows:**

* Change passwords without requiring the old password
* Directly update the verified state
* Modify email without confirmation
* Ignore email visibility settings when viewing records

**Example configurations:**

```go theme={null}
// Allow superusers only (default secure setting)
collection.ManageRule = nil

// Allow the user to manage their own record
collection.ManageRule = types.Pointer("@request.auth.id = id")

// Allow admin users to manage any record
collection.ManageRule = types.Pointer("@request.auth.role = 'admin'")

// Allow users to manage records in their organization
collection.ManageRule = types.Pointer("@request.auth.organizationId = organizationId")
```

The manage rule enables email visibility for matched records:

```go theme={null}
// From apis/record_helpers.go:462
if collection.ManageRule == nil || *collection.ManageRule == "" {
    return nil // no manage rule to check
}

// Fetch the ids of the managed records
resolver := core.NewRecordFieldResolver(app, collection, requestInfo, true)
expr, err := search.FilterData(*collection.ManageRule).BuildExpr(resolver)

// Ignore the email visibility check for the managed records
for _, id := range managedIds {
    if rec, ok := mappedRecords[id]; ok {
        rec.IgnoreEmailVisibility(true)
    }
}
```

<Warning>
  The manage rule is executed **in addition to** the Create and Update API rules. Use it carefully as it grants elevated permissions.
</Warning>

## Filter expression syntax

Rules use the same syntax as the `filter` query parameter:

### Special fields

| Field                | Description                  | Example                            |
| -------------------- | ---------------------------- | ---------------------------------- |
| `@request.auth.id`   | ID of authenticated user     | `@request.auth.id != ''`           |
| `@request.auth.*`    | Any field from auth record   | `@request.auth.role = 'admin'`     |
| `@request.method`    | HTTP method                  | `@request.method = 'GET'`          |
| `@request.query.*`   | Query parameters             | `@request.query.page = '1'`        |
| `@request.data.*`    | Request body data            | `@request.data.status = 'draft'`   |
| `@request.headers.*` | Request headers              | `@request.headers.x_custom != ''`  |
| `@collection.*`      | Reference another collection | `@collection.posts.id ?= authorId` |

<Note>
  `@collection.*` and `@request.*` fields are only available to superusers when used in filter query parameters for security reasons.
</Note>

### Operators

| Operator | Description                 | Example                     |
| -------- | --------------------------- | --------------------------- |
| `=`      | Equals                      | `status = 'active'`         |
| `!=`     | Not equals                  | `role != 'guest'`           |
| `>`      | Greater than                | `age > 18`                  |
| `>=`     | Greater or equal            | `credits ≥ 100`             |
| `<`      | Less than                   | `price < 50`                |
| `<=`     | Less or equal               | `quantity ≤ 10`             |
| `~`      | Contains (case-insensitive) | `name ~ 'john'`             |
| `!~`     | Not contains                | `email !~ '@spam.com'`      |
| `?=`     | Any of (array contains)     | `tags ?= 'featured'`        |
| `?!=`    | None of                     | `categories ?!= 'archived'` |

### Logical operators

```go theme={null}
// AND
"verified = true && status = 'active'"

// OR
"role = 'admin' || role = 'moderator'"

// Grouping
"(role = 'admin' || role = 'moderator') && verified = true"
```

## Common rule patterns

### Public read, authenticated write

```go theme={null}
collection.ListRule = types.Pointer("")   // Anyone can list
collection.ViewRule = types.Pointer("")   // Anyone can view
collection.CreateRule = types.Pointer("@request.auth.id != '") // Only authenticated users can create
collection.UpdateRule = types.Pointer("@request.auth.id = userId") // Only owner can update
collection.DeleteRule = types.Pointer("@request.auth.id = userId") // Only owner can delete
```

### Owner-only access

```go theme={null}
rule := "@request.auth.id = userId"

collection.ListRule = types.Pointer(rule)
collection.ViewRule = types.Pointer(rule)
collection.CreateRule = types.Pointer("@request.auth.id != ''")
collection.UpdateRule = types.Pointer(rule)
collection.DeleteRule = types.Pointer(rule)
```

### Role-based access

```go theme={null}
// Admin can do everything
adminRule := "@request.auth.role = 'admin'"

// Members can read, admins can write
collection.ListRule = types.Pointer("@request.auth.id != ''")
collection.ViewRule = types.Pointer("@request.auth.id != ''")
collection.CreateRule = types.Pointer(adminRule)
collection.UpdateRule = types.Pointer(adminRule)
collection.DeleteRule = types.Pointer(adminRule)
```

### Organization/tenant isolation

```go theme={null}
// Users can only access records in their organization
orgRule := "@request.auth.organizationId = organizationId"

collection.ListRule = types.Pointer(orgRule)
collection.ViewRule = types.Pointer(orgRule)
collection.CreateRule = types.Pointer("@request.auth.id != '' && @request.data.organizationId = @request.auth.organizationId")
collection.UpdateRule = types.Pointer(orgRule)
collection.DeleteRule = types.Pointer(orgRule)
```

### Status-based access

```go theme={null}
// Published posts are public, drafts are owner-only
collection.ListRule = types.Pointer("status = 'published' || (@request.auth.id = userId && status = 'draft')")
collection.ViewRule = types.Pointer("status = 'published' || @request.auth.id = userId")
collection.CreateRule = types.Pointer("@request.auth.id != ''")
collection.UpdateRule = types.Pointer("@request.auth.id = userId")
collection.DeleteRule = types.Pointer("@request.auth.id = userId")
```

## Rule validation

PocketBase validates rules during collection save:

```go theme={null}
// From core/collection_model_auth_options.go:151
validation.Field(
    &o.AuthRule,
    validation.By(cv.checkRule),
    validation.By(cv.ensureNoSystemRuleChange(cv.original.AuthRule)),
)
```

System collection rules cannot be changed:

```go theme={null}
// From core/collection_validate.go:517
if c.IsSystem() && c.hasSystemRuleChanges() {
    return validation.NewError(
        "validation_collection_system_rule_change",
        "System collection API rule cannot be changed."
    )
}
```

<Warning>
  You cannot modify API rules for system collections like `_superusers`, `_mfas`, `_otps`, etc.
</Warning>

## Checking access programmatically

You can check if a user can access a record using the `CanAccessRecord` method:

```go theme={null}
requestInfo, _ := e.RequestInfo()

ok, err := app.CanAccessRecord(record, requestInfo, collection.ViewRule)
if !ok {
    return errors.New("access denied")
}
```

## List vs View rule differences

The key difference between List and View rules:

* **List Rule**: Acts as a **filter** on the query results. Records not matching the rule are excluded from the list.
* **View Rule**: Acts as a **permission check**. If the rule doesn't match, the request is denied with a 403/404 error.

```go theme={null}
// List rule example
collection.ListRule = types.Pointer("status = 'published'")
// Result: Only published records are returned in list queries

// View rule example  
collection.ViewRule = types.Pointer("@request.auth.id = userId")
// Result: Returns 404 if user tries to view someone else's record
```

<Info>
  The List rule is applied as a query filter, while the View rule is a binary permission check.
</Info>

## Expansion and rules

When expanding relations, PocketBase applies the View rule of the related collection:

```go theme={null}
// From apis/record_helpers.go:381
if relCollection.ViewRule == nil {
    return fmt.Errorf("only superusers can view collection %q records", 
        relCollection.Name)
}

if *relCollection.ViewRule != "" {
    resolver := core.NewRecordFieldResolver(app, relCollection, requestInfoPtr, true)
    expr, err := search.FilterData(*(relCollection.ViewRule)).BuildExpr(resolver)
    if err != nil {
        return err
    }
    q.AndWhere(expr)
}
```

## Security best practices

<Warning>
  Always test your rules thoroughly. Incorrect rules can expose sensitive data or prevent legitimate access.
</Warning>

### Best practices

1. **Start restrictive**: Begin with `nil` rules and open up access as needed.

2. **Validate both sides**: Check both `@request.auth` and record fields in rules.

3. **Use manage rule carefully**: Only grant manage permissions to trusted users.

4. **Test as different users**: Always test rules from the perspective of different user roles.

5. **Avoid public write access**: Be very cautious about setting Create/Update/Delete rules to `""`.

6. **Consider cascading deletes**: Ensure delete rules account for related records.

7. **Audit rule changes**: Track changes to collection rules in production.

## Custom rule validation

You can add custom rule validation using hooks:

```go theme={null}
app.OnCollectionValidate().Bind(&hook.Handler[*core.CollectionEvent]{
    Func: func(e *core.CollectionEvent) error {
        if e.Collection.IsAuth() && e.Collection.AuthRule == nil {
            log.Printf("Warning: Auth collection %s has authentication disabled",
                e.Collection.Name)
        }
        
        // Custom validation logic
        if e.Collection.Name == "sensitive_data" && 
           e.Collection.ListRule != nil && 
           *e.Collection.ListRule == "" {
            return errors.New("sensitive_data cannot have public list access")
        }
        
        return e.Next()
    },
})
```

## Rule migration example

When migrating from v0.22 to later versions:

```go theme={null}
// From migrations/1717233556_v0.23_migrate.go:361
if collection.Name == core.CollectionNameSuperusers {
    collection.AuthRule = types.Pointer("verified=true")
} else {
    collection.AuthRule = types.Pointer("")
}
```

## Related topics

<CardGroup cols={2}>
  <Card title="Collections" icon="database" href="/collections/overview">
    Learn about collection types
  </Card>

  <Card title="Auth overview" icon="lock" href="/auth/overview">
    Authentication system overview
  </Card>

  <Card title="Query syntax" icon="filter" href="/api/records">
    Filter expression reference
  </Card>

  <Card title="Hooks" icon="hook" href="/extending/event-hooks">
    Customize behavior with hooks
  </Card>
</CardGroup>
