Go records, queries, and database operations
Use PocketBase Go records, typed query results, SQL, transactions, and collection APIs safely.
Use this reference when Go extension code must read, create, update, delete, or query PocketBase data. It covers the core.Record model, collection helpers, RecordQuery, the database query builder, typed results, and transaction rules.
Before you begin
You need a Go application created with pocketbase.New() or pocketbase.NewWithConfig, and collections such as articles must already exist. The examples use placeholder IDs and safe sample values; replace them with values from your own data.
Work with records
Set and read fields
Use Set for one field or Load for a map of fields. Field modifiers such as users+ append to an existing value. Use typed accessors when the expected type is known.
record.Set("title", "A release note")
record.Set("users+", "RECORD_ID")
record.Load(map[string]any{"active": true})
title := record.GetString("title")
active := record.GetBool("active")
publishedAt := record.GetDateTime("published")Available typed accessors include GetBool, GetString, GetInt, GetInt64, GetFloat, GetDateTime, and GetStringSlice. Use GetUnsavedFiles to inspect newly supplied files, UnmarshalJSONField for JSON fields, and PublicExport to obtain the public-safe field map.
Fetch records
Single-record helpers return nil and sql.ErrNoRows when there is no match. Multiple-record helpers return an empty slice and a nil error when no records match.
record, err := app.FindRecordById("articles", "RECORD_ID")
record, err = app.FindFirstRecordByData("articles", "slug", "release-notes")
records, err := app.FindRecordsByFilter(
"articles",
"status = 'public' && category = {:category}",
"-published",
10,
0,
dbx.Params{"category": "news"},
)Use {:name} placeholders with dbx.Params for untrusted filter values. Other helpers include FindRecordsByIds, FindAllRecords, and CountRecords. For auth collections, use FindAuthRecordByEmail or FindAuthRecordByToken with the appropriate token type.
Create, update, and delete
Create a record from its collection, set its fields, and persist it with Save.
collection, err := app.FindCollectionByNameOrId("articles")
if err != nil { return err }
record := core.NewRecord(collection)
record.Set("title", "A release note")
record.Set("active", true)
record.Set("slug:autogenerate", "release-")
if err := app.Save(record); err != nil { return err }
record.Set("title", "Updated release note")
if err := app.Save(record); err != nil { return err }
if err := app.Delete(record); err != nil { return err }Save validates fields. Use SaveNoValidate only when intentionally bypassing field validation. For file fields, use a *filesystem.File or slice of files and set it before saving; collection file validation still applies.
Use copies and expansions
Original returns the original database state, Fresh returns the latest data without expansions or custom fields, and Clone copies the current record with collection data, expansions, and visibility flags. Load relation data with ExpandRecord or ExpandRecords, then read it with ExpandedOne or ExpandedAll.
record, err := app.FindFirstRecordByData("articles", "slug", "release-notes")
if err != nil { return err }
if errs := app.ExpandRecord(record, []string{"author", "categories"}, nil); len(errs) > 0 {
return fmt.Errorf("expand failed: %v", errs)
}
author := record.ExpandedOne("author")
categories := record.ExpandedAll("categories")Use Hide and Unhide to control serialization visibility. Custom fields require WithCustomData(true) before they are included in public serialization.
Query the database
Execute typed SQL
app.DB().NewQuery(sql) returns a database builder. Use One for one typed result and All for a slice. Bind values with named placeholders rather than interpolating input.
type ArticleSummary struct {
ID string `db:"id" json:"id"`
Title string `db:"title" json:"title"`
Active bool `db:"active" json:"active"`
}
var rows []ArticleSummary
err := app.DB().NewQuery(
"SELECT id, title, active FROM articles WHERE title LIKE {:term}",
).Bind(dbx.Params{"term": "%Go%"}).All(&rows)For One, pass a pointer to one struct. Supported field mappings include standard Go fields and PocketBase types such as types.DateTime and types.JSONArray[string].
Build a query
The query builder supports Select, AndSelect, Distinct, From, Join, Where, AndWhere, OrWhere, OrderBy, AndOrderBy, GroupBy, AndGroupBy, Having, AndHaving, OrHaving, Limit, and Offset.
var articles []ArticleSummary
err := app.DB().Select("id", "title").
From("articles").
Where(dbx.HashExp{"active": true}).
OrderBy("title ASC").
Limit(20).
Offset(0).
All(&articles)For record-shaped results, app.RecordQuery("articles") returns a select builder that can be consumed with the same methods as a database query.
Run a transaction
Use RunInTransaction when several writes must commit together. PocketBase persists the operations only when the callback returns nil.
err := app.RunInTransaction(func(txApp core.App) error {
for _, title := range []string{"First", "Second"} {
r := core.NewRecord(collection)
r.Set("title", title)
if err := txApp.Save(r); err != nil { return err }
}
return nil
})Always use the callback's txApp, including for nested transactions. Reusing app can deadlock because PocketBase permits one writer or transaction at a time. Keep slow work such as email or external service calls outside the transaction.
Verify access and tokens
For a custom route, use CanAccessRecord(record, requestInfo, rule) with the collection's rule before returning a record. Record token helpers can issue and validate auth-related token types; token duration is controlled by the auth collection options, except for static auth tokens.
Next steps
Continue with Go hooks and event lifecycle to run this logic at the correct application or model event, or review Go filesystem and email when records include files or notifications.