Skip to main content
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
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:
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
}
The record id is automatically generated when you save a new record. It’s a 15-character lowercase alphanumeric string by default.

Reading field values

PocketBase provides multiple methods for accessing record data:

Basic getters

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

// Get raw value without field getters processing
rawValue := record.GetRaw("title")

JSON unmarshaling

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:
// 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:
// 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

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

// 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:
// 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
Changing the password or email automatically refreshes the tokenKey, which invalidates all existing authentication tokens for that user.

Relations and expand

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

Expanding relations

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

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:
// 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:
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

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

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:
record.IgnoreUnchangedFields(true)
record.Set("title", "New Title")
// Only the title field will be updated in the database
app.Save(record)

Deleting records

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

if err := app.Delete(record); err != nil {
    return err
}
Deleting a record also deletes associated files and handles cascade deletion for related records if configured.

Record serialization

Public export

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

Custom visibility

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

// Marshal to JSON (uses PublicExport)
jsonData, err := json.Marshal(record)

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

Record state management

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

Always validate data before setting it on records. Use collection field constraints and custom validation logic to ensure data integrity.
Prefer GetString(), GetInt(), etc., over Get() to avoid type assertion errors and benefit from automatic type conversion.
Always check if expanded relations exist before accessing them. Missing or deleted related records can cause errors.
For multiple files, consider uploading them in batches. Large file uploads should be handled with proper error handling and retry logic.
Implement business logic in record hooks rather than scattering it throughout your codebase. This ensures consistency and maintainability.
Only use SaveNoValidate() when you’re certain the data is valid, such as system-generated updates or data migrations.