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

> Fine-grained access control with PocketBase API rules

API rules provide declarative, expression-based access control for your PocketBase collections. They determine who can list, view, create, update, and delete records through the API.

## Overview

Each collection has five API rule types:

* **ListRule** - Controls access to list/search operations
* **ViewRule** - Controls access to viewing individual records
* **CreateRule** - Controls who can create new records
* **UpdateRule** - Controls who can update existing records
* **DeleteRule** - Controls who can delete records

```go theme={null}
collection.ListRule = types.Pointer("@request.auth.id != ''")
collection.ViewRule = types.Pointer("")
collection.CreateRule = types.Pointer("@request.auth.id != ''")
collection.UpdateRule = types.Pointer("@request.auth.id = author")
collection.DeleteRule = types.Pointer("@request.auth.id = author")
```

## Rule syntax

API rules use a simple expression language:

```go theme={null}
// Allow everyone
collection.ListRule = types.Pointer("")

// Deny everyone
collection.ListRule = nil

// Conditional access
collection.ListRule = types.Pointer("published = true")
```

<Info>
  Rules use `nil` (not set) to deny all access, empty string `""` to allow all access, and expressions for conditional access.
</Info>

## Request context

Rules have access to request information via `@request`:

### Authentication

```go theme={null}
// Check if user is authenticated
"@request.auth.id != ''"

// Check user ID
"@request.auth.id = 'USER_ID'"

// Check user field
"@request.auth.role = 'admin'"
"@request.auth.verified = true"
"@request.auth.email = 'admin@example.com'"
```

### Request data

```go theme={null}
// Access submitted data (for Create/Update rules)
"@request.data.published = true"
"@request.data.title != ''"
"@request.data.price > 0"
```

### Request method

```go theme={null}
// Check HTTP method
"@request.method = 'GET'"
"@request.method != 'DELETE'"
```

### Query parameters

```go theme={null}
// Access URL query parameters
"@request.query.status = 'active'"
"@request.query.page = '1'"
```

### Headers

```go theme={null}
// Access request headers
"@request.headers.x-custom-header = 'value'"
```

## Record fields

Rules can reference record fields directly:

```go theme={null}
// Simple field comparison
"published = true"
"status = 'active'"
"price > 100"

// Date comparisons
"created > '2024-01-01'"
"expires < @now"

// Array contains
"tags ?= 'featured'"
"roles ?= 'admin'"
```

## Operators

### Comparison operators

```go theme={null}
"count = 10"      // Equal
"count != 5"      // Not equal
"price > 100"     // Greater than
"price >= 100"    // Greater than or equal
"price < 50"      // Less than
"price <= 50"     // Less than or equal
```

### Logical operators

```go theme={null}
"published = true && featured = true"  // AND
"status = 'active' || status = 'pending'"  // OR
"!(archived = true)"  // NOT
```

### String operators

```go theme={null}
"title ~ 'hello'"     // Contains (case-sensitive)
"title ~~ 'hello'"    // Contains (case-insensitive)
"email ?~ '.+@.+'"    // Regex match
"email !~ 'test'"     // Not contains
```

### Array operators

```go theme={null}
"tags ?= 'featured'"   // Array contains
"tags ?!= 'hidden'"    // Array not contains
"roles ?~ 'admin|mod'" // Array contains (regex)
```

### Relation operators

```go theme={null}
"author.role = 'admin'"              // Relation field access
"category.name = 'Technology'"       // Nested relation
"comments.user.verified = true"      // Deep relation
"tags.id ?= @request.auth.favoriteTag"  // Relation with auth
```

## Special variables

### @now

Current date/time:

```go theme={null}
"created > @now - 86400"  // Created in last 24 hours
"expires < @now"          // Already expired
"startDate <= @now && endDate >= @now"  // Currently active
```

### @todayStart / @todayEnd

Today's date boundaries:

```go theme={null}
"created >= @todayStart"  // Created today
"created < @todayStart"   // Created before today
```

### @monthStart / @monthEnd

Current month boundaries:

```go theme={null}
"created >= @monthStart"  // Created this month
```

### @yearStart / @yearEnd

Current year boundaries:

```go theme={null}
"created >= @yearStart"   // Created this year
```

## Common patterns

### Public read, authenticated write

```go theme={null}
collection.ListRule = types.Pointer("")
collection.ViewRule = types.Pointer("")
collection.CreateRule = types.Pointer("@request.auth.id != ''")
collection.UpdateRule = types.Pointer("@request.auth.id != ''")
collection.DeleteRule = types.Pointer("@request.auth.id != ''")
```

### Owner-only access

```go theme={null}
collection.ListRule = types.Pointer("@request.auth.id != '' && author = @request.auth.id")
collection.ViewRule = types.Pointer("author = @request.auth.id")
collection.CreateRule = types.Pointer("@request.auth.id != ''")
collection.UpdateRule = types.Pointer("author = @request.auth.id")
collection.DeleteRule = types.Pointer("author = @request.auth.id")
```

### Published content

```go theme={null}
collection.ListRule = types.Pointer(
    "published = true || @request.auth.id = author",
)
collection.ViewRule = types.Pointer(
    "published = true || @request.auth.id = author",
)
collection.UpdateRule = types.Pointer("@request.auth.id = author")
collection.DeleteRule = types.Pointer("@request.auth.id = author")
```

### Admin override

```go theme={null}
collection.UpdateRule = types.Pointer(
    "@request.auth.id = author || @request.auth.role = 'admin'",
)
collection.DeleteRule = types.Pointer(
    "@request.auth.id = author || @request.auth.role = 'admin'",
)
```

### Verified users only

```go theme={null}
collection.CreateRule = types.Pointer(
    "@request.auth.id != '' && @request.auth.verified = true",
)
```

### Prevent self-deletion

```go theme={null}
collection.DeleteRule = types.Pointer(
    "@request.auth.id != '' && @request.auth.id != id",
)
```

## Rule validation

Validate rules before saving:

```go theme={null}
// Rules are validated automatically during collection save
err := app.Save(collection)
if err != nil {
    // Handle validation error
}
```

### Testing rules

Test rules programmatically:

```go theme={null}
// Check if user can access record
canAccess, err := app.CanAccessRecord(
    record,
    requestInfo,
    collection.ViewRule,
)
```

## CreateRule specifics

CreateRule has special behavior for owner fields:

```go theme={null}
// Auto-set author field
collection.CreateRule = types.Pointer(
    "@request.auth.id != '' && @request.data.author = @request.auth.id",
)
```

This ensures users can only create records assigned to themselves.

## UpdateRule specifics

UpdateRule evaluates against the **original** record state:

```go theme={null}
// User can update their own records
collection.UpdateRule = types.Pointer("author = @request.auth.id")

// Prevent changing ownership
collection.UpdateRule = types.Pointer(
    "author = @request.auth.id && @request.data.author = author",
)

// Prevent status changes
collection.UpdateRule = types.Pointer(
    "@request.auth.id = author && @request.data.status = status",
)
```

## Performance considerations

<AccordionGroup>
  <Accordion title="Index filtered fields">
    Create database indexes for fields used in API rules to improve query performance.
  </Accordion>

  <Accordion title="Avoid complex relation chains">
    Deep relation traversals (e.g., `post.author.team.organization.owner`) can be slow. Keep rules simple when possible.
  </Accordion>

  <Accordion title="Use specific rules over wildcards">
    Specific rules like `author = @request.auth.id` are faster than complex OR conditions.
  </Accordion>

  <Accordion title="Optimize @request.data checks">
    For CreateRule and UpdateRule, validate submitted data efficiently by checking required fields first.
  </Accordion>
</AccordionGroup>

## Security best practices

<AccordionGroup>
  <Accordion title="Start restrictive">
    Begin with strict rules (nil) and gradually relax them as needed. It's easier to grant access than to remove it.
  </Accordion>

  <Accordion title="Validate ownership">
    Always verify record ownership in UpdateRule and DeleteRule to prevent unauthorized modifications.
  </Accordion>

  <Accordion title="Protect sensitive fields">
    Use field-level visibility settings alongside API rules to hide sensitive data.
  </Accordion>

  <Accordion title="Test rules thoroughly">
    Test all rule combinations with different user roles and authentication states.
  </Accordion>

  <Accordion title="Audit rule changes">
    Log and review changes to API rules, especially for production collections.
  </Accordion>

  <Accordion title="Use manage rules carefully">
    Manage rules grant elevated permissions. Apply them conservatively and validate admin status properly.
  </Accordion>

  <Accordion title="Prevent data leakage">
    Ensure ListRule and ViewRule don't expose records users shouldn't see, even if they guess IDs.
  </Accordion>
</AccordionGroup>

## Advanced examples

### Multi-tenant system

```go theme={null}
collection.ListRule = types.Pointer(
    "@request.auth.id != '' && @request.auth.organizationId = organizationId",
)
collection.CreateRule = types.Pointer(
    "@request.auth.id != '' && " +
    "@request.data.organizationId = @request.auth.organizationId",
)
```

### Time-based access

```go theme={null}
collection.ViewRule = types.Pointer(
    "publishDate <= @now && (expiryDate = '' || expiryDate >= @now)",
)
```

### Role-based permissions

```go theme={null}
collection.CreateRule = types.Pointer(
    "@request.auth.id != '' && @request.auth.roles ?= 'editor'",
)
collection.DeleteRule = types.Pointer(
    "@request.auth.id != '' && (" +
    "  @request.auth.roles ?= 'admin' ||" +
    "  (@request.auth.roles ?= 'editor' && author = @request.auth.id)" +
    ")",
)
```

### Collaborative editing

```go theme={null}
collection.UpdateRule = types.Pointer(
    "@request.auth.id != '' && (" +
    "  author = @request.auth.id ||" +
    "  collaborators ?= @request.auth.id" +
    ")",
)
```

### Draft vs published

```go theme={null}
collection.ListRule = types.Pointer(
    "(published = true) || " +
    "(@request.auth.id != '' && author = @request.auth.id)",
)
collection.ViewRule = types.Pointer(
    "published = true || " +
    "(@request.auth.id != '' && author = @request.auth.id)",
)
```

## Debugging rules

Enable query logging to debug rule evaluation:

```go theme={null}
app.OnRecordListRequest().Bind(&hook.Handler[*core.RecordListRequestEvent]{
    Func: func(e *core.RecordListRequestEvent) error {
        e.App.Logger().Debug(
            "List request",
            "collection", e.Collection.Name,
            "rule", cast.ToString(e.Collection.ListRule),
            "auth", e.Auth != nil,
        )
        return e.Next()
    },
})
```

<Tip>
  Use the PocketBase Admin UI to test API rules interactively. The "API Preview" feature shows exactly how rules affect API responses.
</Tip>
