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

# Records

> Understanding PocketBase records and how to work with data

Records are individual data entries within a collection. Each record contains field values defined by its parent collection's schema and provides methods for data access, manipulation, and validation.

## Record structure

A record consists of:

* **Collection reference** - The parent collection defining the schema
* **Field data** - Values for each field defined in the collection
* **System fields** - Auto-managed fields like `id`, `created`, `updated`
* **Expand data** - Resolved relation field data
* **Custom data** - Additional fields not defined in the collection schema

```go theme={null}
type Record struct {
    collection       *Collection
    data             map[string]any
    expand           map[string]any
    originalData     map[string]any  // For tracking changes
    
    BaseModel  // Includes Id, Created, Updated
}
```

## Creating records

You create new records through the collection:

```go theme={null}
collection, _ := app.FindCollectionByNameOrId("posts")
record := core.NewRecord(collection)

// Set field values
record.Set("title", "Hello World")
record.Set("content", "This is my first post")
record.Set("published", true)

// Save to database
if err := app.Save(record); err != nil {
    return err
}
```

<Info>
  The record `id` is automatically generated when you save a new record. It's a 15-character lowercase alphanumeric string by default.
</Info>

## Reading field values

PocketBase provides multiple methods for accessing record data:

### Basic getters

```go theme={null}
// Generic getter (returns any)
value := record.Get("title")

// Type-specific getters
title := record.GetString("title")
count := record.GetInt("views")
published := record.GetBool("published")
price := record.GetFloat("price")
createdAt := record.GetDateTime("created")
tags := record.GetStringSlice("tags")
```

### Raw getters

```go theme={null}
// Get raw value without field getters processing
rawValue := record.GetRaw("title")
```

### JSON unmarshaling

```go theme={null}
type Meta struct {
    Views  int    `json:"views"`
    Author string `json:"author"`
}

var meta Meta
err := record.UnmarshalJSONField("metadata", &meta)
```

## Setting field values

You can set field values using various methods:

```go theme={null}
// Basic setter
record.Set("title", "Updated Title")

// Bulk load from map
record.Load(map[string]any{
    "title":     "New Title",
    "content":   "New content",
    "published": true,
})

// Set raw value (bypasses field setters)
record.SetRaw("title", "Direct Value")
```

### Field modifiers

Some fields support modifier operations:

```go theme={null}
// For numeric fields
record.Set("views+", 1)     // Increment by 1
record.Set("views-", 5)     // Decrement by 5

// For array/relation fields
record.Set("tags+", "new")  // Append
record.Set("+tags", "new")  // Prepend
record.Set("tags-", "old")  // Remove

// For file fields
record.Set("images+", file) // Add file
record.Set("images-", "old_image.jpg") // Remove file
```

## Working with files

File fields require special handling:

### Uploading files

```go theme={null}
file := &filesystem.File{
    Name:   "document.pdf",
    Reader: fileReader,
}

record.Set("attachments", file)
// or for multiple files
record.Set("attachments", []*filesystem.File{file1, file2})

// Save uploads the files automatically
app.Save(record)
```

### Accessing uploaded files

```go theme={null}
// Get files pending upload
files := record.GetUnsavedFiles("attachments")

// Modify file names before saving
for _, f := range files {
    f.Name = "prefix_" + f.Name
}

app.Save(record)
```

### File URLs

Files are stored at:

```
/api/files/{collectionId}/{recordId}/{filename}
```

For protected files, you need a file token:

```
/api/files/{collectionId}/{recordId}/{filename}?token={fileToken}
```

## Auth record helpers

Auth collections have additional convenience methods:

```go theme={null}
// Email
email := record.Email()
record.SetEmail("user@example.com")

// Email visibility
visible := record.EmailVisibility()
record.SetEmailVisibility(true)

// Verified status
verified := record.Verified()
record.SetVerified(true)

// Password
record.SetPassword("newpassword123")
record.SetRandomPassword()  // Generates secure random password

// Password validation
if record.ValidatePassword("userInputPassword") {
    // Password is correct
}

// Token key (for session management)
tokenKey := record.TokenKey()
record.RefreshTokenKey()  // Invalidates all existing tokens
```

<Warning>
  Changing the password or email automatically refreshes the `tokenKey`, which invalidates all existing authentication tokens for that user.
</Warning>

## Relations and expand

Relation fields reference records from other collections. You can expand (resolve) these relations:

### Expanding relations

```go theme={null}
// Fetch record with expanded relations
record, err := app.FindRecordById("posts", "RECORD_ID", 
    db.Expand("author", "category")
)

// Access expanded data
author := record.ExpandedOne("author")
if author != nil {
    authorName := author.GetString("name")
}

// For multiple relations
comments := record.ExpandedAll("comments")
for _, comment := range comments {
    text := comment.GetString("text")
}
```

### Setting expand data manually

```go theme={null}
record.SetExpand(map[string]any{
    "author": authorRecord,
    "tags":   []core.Record{tag1, tag2},
})

// Merge expand data
record.MergeExpand(map[string]any{
    "category": categoryRecord,
})
```

## Record validation

Records are validated automatically during save operations:

```go theme={null}
// Validate without saving
err := app.Validate(record)
if err != nil {
    // Handle validation errors
    if validationErr, ok := err.(validation.Errors); ok {
        for field, fieldErr := range validationErr {
            fmt.Printf("Field %s: %v\n", field, fieldErr)
        }
    }
}

// Save with validation (default)
err := app.Save(record)

// Save without validation (use with caution)
err := app.SaveNoValidate(record)
```

## Record hooks

Intercept record operations with hooks:

```go theme={null}
app.OnRecordCreate().Bind(&hook.Handler[*core.RecordEvent]{
    Func: func(e *core.RecordEvent) error {
        // Modify record before creation
        if e.Record.Collection().Name == "posts" {
            e.Record.Set("author", e.Auth.Id)
        }
        return e.Next()
    },
})

app.OnRecordAfterCreateSuccess().Bind(&hook.Handler[*core.RecordEvent]{
    Func: func(e *core.RecordEvent) error {
        // Perform actions after successful creation
        // e.g., send notification, update cache, etc.
        return e.Next()
    },
})
```

**Available record hooks:**

* `OnRecordValidate` - Before validation
* `OnRecordCreate` / `OnRecordUpdate` / `OnRecordDelete` - Before operation
* `OnRecordCreateExecute` / `OnRecordUpdateExecute` / `OnRecordDeleteExecute` - During database write
* `OnRecordAfterCreateSuccess` / `OnRecordAfterUpdateSuccess` / `OnRecordAfterDeleteSuccess` - After success
* `OnRecordAfterCreateError` / `OnRecordAfterUpdateError` / `OnRecordAfterDeleteError` - After error

## Querying records

```go theme={null}
// Find by ID
record, err := app.FindRecordById("posts", "RECORD_ID")

// Find first matching record
record, err := app.FindFirstRecordByFilter(
    "posts",
    "title = 'Hello World'",
)

// Find with data filter
record, err := app.FindFirstRecordByData(
    "posts",
    "title", "Hello World",
)

// Complex queries
records := []*core.Record{}
err := app.RecordQuery("posts").
    AndWhere(dbx.HashExp{"published": true}).
    OrderBy("created DESC").
    Limit(10).
    All(&records)
```

## Updating records

```go theme={null}
record, err := app.FindRecordById("posts", "RECORD_ID")
if err != nil {
    return err
}

// Modify fields
record.Set("title", "Updated Title")
record.Set("views+", 1)

// Save changes
if err := app.Save(record); err != nil {
    return err
}
```

### Partial updates

To update only changed fields:

```go theme={null}
record.IgnoreUnchangedFields(true)
record.Set("title", "New Title")
// Only the title field will be updated in the database
app.Save(record)
```

## Deleting records

```go theme={null}
record, err := app.FindRecordById("posts", "RECORD_ID")
if err != nil {
    return err
}

if err := app.Delete(record); err != nil {
    return err
}
```

<Info>
  Deleting a record also deletes associated files and handles cascade deletion for related records if configured.
</Info>

## Record serialization

### Public export

```go theme={null}
// Export safe for public API response
publicData := record.PublicExport()
// Returns map[string]any with only non-hidden fields
```

### Custom visibility

```go theme={null}
// Hide specific fields
record.Hide("email", "password")

// Unhide fields (even if marked hidden in schema)
record.Unhide("email")

// Include custom/unknown fields
record.WithCustomData(true)

// Ignore email visibility for auth records
record.IgnoreEmailVisibility(true)

publicData := record.PublicExport()
```

### JSON marshaling

```go theme={null}
// Marshal to JSON (uses PublicExport)
jsonData, err := json.Marshal(record)

// Unmarshal from JSON
err := json.Unmarshal(jsonData, &record)
```

## Record state management

```go theme={null}
// Check if record is new (not yet saved)
if record.IsNew() {
    // Handle new record
}

// Get original data (as loaded from database)
original := record.Original()

// Get fresh copy (current state, no expand/custom data)
fresh := record.Fresh()

// Get full clone (including expand and custom data)
clone := record.Clone()

// Get collection
collection := record.Collection()

// Get table name
tableName := record.TableName()

// Get files path
filesPath := record.BaseFilesPath()
// Returns: {collectionId}/{recordId}
```

## Best practices

<AccordionGroup>
  <Accordion title="Validate user input">
    Always validate data before setting it on records. Use collection field constraints and custom validation logic to ensure data integrity.
  </Accordion>

  <Accordion title="Use type-specific getters">
    Prefer `GetString()`, `GetInt()`, etc., over `Get()` to avoid type assertion errors and benefit from automatic type conversion.
  </Accordion>

  <Accordion title="Handle relations carefully">
    Always check if expanded relations exist before accessing them. Missing or deleted related records can cause errors.
  </Accordion>

  <Accordion title="Optimize file uploads">
    For multiple files, consider uploading them in batches. Large file uploads should be handled with proper error handling and retry logic.
  </Accordion>

  <Accordion title="Use hooks for side effects">
    Implement business logic in record hooks rather than scattering it throughout your codebase. This ensures consistency and maintainability.
  </Accordion>

  <Accordion title="Be cautious with SaveNoValidate">
    Only use `SaveNoValidate()` when you're certain the data is valid, such as system-generated updates or data migrations.
  </Accordion>
</AccordionGroup>
