The most common task when extending PocketBase probably would be querying and operating with your collection records.
Get/Set record fields
// export the public safe record fields as map[string]any
record.publicExport()
// returns a new model copy populated with the original/intial record data
// (could be useful if you want to compare old and new field values)
record.originalCopy()
// returns a copy of the current record model populated only
// with its latest data state and everything else reset to the defaults
record.cleanCopy()
// set the value of a single record field
record.set("someField", 123)
// bulk set fields from a map
record.load(data)
// retrieve a single record field value
record.get("someField") // -> as any
record.getBool("someField") // -> as bool
record.getString("someField") // -> as string
record.getInt("someField") // -> as int
record.getFloat("someField") // -> as float64
record.getTime("someField") // -> as time.Time
record.getDateTime("someField") // -> as types.DateTime
record.getStringSlice("someField") // -> as []string
// unmarshal a single json field value into the provided result
const result = new DynamicModel({ ... })
record.unmarshalJSONField("someJsonField", result)
// retrieve a single or multiple expanded data
record.expandedOne("author") // -> as null|Record
record.expandedAll("categories") // -> as []Record
// auth records only
// ---
record.setPassword("123456")
record.validatePassword("123456")
record.passwordHash()
// ---
record.username()
record.setUsername("john.doe")
// ---
record.email()
record.setEmail("test@example.com")
// ---
record.emailVisibility()
record.setEmailVisibility(false)
// ---
record.verified()
record.setVerified(false)
// ---
record.tokenKey()
record.setTokenKey("ABCD123")
record.refreshTokenKey() // sets autogenerated TokenKey
// ---
record.lastResetSentAt()
record.setLastResetSentAt(new DateTime())
// ---
record.lastVerificationSentAt()
record.setLastVerificationSentAt(new DateTime())
Fetch records
Fetch single record
// retrieve a single "articles" collection record by its id
const record = $app.dao().findRecordById("articles", "RECORD_ID")
// retrieve a single "articles" collection record by a single key-value pair
const record = $app.dao().findFirstRecordByData("articles", "slug", "test")
// retrieve a single "articles" collection record by a string filter expression
// (use "{:placeholder}" to safely bind untrusted user input parameters)
const record = $app.dao().findFirstRecordByFilter(
"articles", "status = 'public' && category = {:category}",
{ category: "news" },
)
Fetch multiple records
// retrieve multiple "articles" collection records by their ids
const records = $app.dao().findRecordsByIds("articles", ["RECORD_ID1", "RECORD_ID2"])
// retrieve multiple "articles" collection records by a custom dbx expression(s)
// (for all avalaible expressions, please check the Database guide)
const records = $app.dao().findRecordsByExpr("articles",
$dbx.exp("LOWER(username) = {:username}", { "username": "John.Doe" }),
$dbx.hashExp({ status: "pending" })
)
// retrieve multiple "articles" collection records by a string filter expression
// (use "{:placeholder}" to safely bind untrusted user input parameters)
const records = $app.dao().findRecordsByFilter(
"articles", // collection
"status = 'public' && category = {:category}", // filter
"-publised", // sort
10, // limit
0, // offset
{ category: "news" }, // optional filter params
)
Fetch auth records
// retrieve a single auth collection record by its email
const user = $app.dao().findAuthRecordByEmail("users", "test@example.com")
// retrieve a single auth collection record by its username (case insensitive)
const user = $app.dao().findAuthRecordByUsername("users", "John.Doe")
// retrieve a single auth collection record by its JWT (auth, password reset, etc.)
const user = $app.dao().findAuthRecordByToken("YOUR_TOKEN", $app.settings().recordAuthToken.secret)
Custom record query
In addition to the above read and write helpers, you can also create custom Record model queries using
Dao.recordQuery(collection)
method. It returns a DB builder that can be used with the same methods described in the
Database guide.
For retrieving a single Record model with the one()
executor, you can use a
blank new Record()
model to populate the result in.
function findTopArticle() {
const record = new Record();
$app.dao().recordQuery("articles")
.andWhere($dbx.hashExp({ "status": "active" }))
.orderBy("rank ASC")
.limit(1)
.one(record)
return record
}
const article = findTopArticle()
For retrieving multiple Record models with the all()
executor, you can use
arrayOf(new Record)
to create an array placeholder in which to populate the resolved DB result.
// the below is identical to
// dao.findRecordsByFilter("articles", "status = 'active'", '-published', 10)
// but allows more advanced use cases and filtering (aggregations, subqueries, etc.)
function findLatestArticles() {
const records = arrayOf(new Record);
$app.dao().recordQuery("articles")
.andWhere($dbx.hashExp({ "status": "active" }))
.orderBy("published DESC")
.limit(10)
.all(records)
return records
}
const articles = findLatestArticles()
Create new record
Create new record WITHOUT data validations
const collection = $app.dao().findCollectionByNameOrId("articles")
const record = new Record(collection, {
// bulk load the record data during initialization
"title": "Lorem ipsum",
"active": true
})
// or load individual fields separately
record.set("someOtherField", 123)
$app.dao().saveRecord(record)
Create new record WITH data validations
const collection = $app.dao().findCollectionByNameOrId("articles")
const record = new Record(collection)
const form = new RecordUpsertForm($app, record)
// or form.loadRequest(request, "")
form.loadData({
"title": "Lorem ipsum",
"active": true,
"someOtherField": 123,
})
// manually upload file(s)
const f1 = $filesystem.fileFromPath("/path/to/file1")
const f2 = $filesystem.fileFromPath("/path/to/file2")
form.addFiles("yourFileField1", f1, f2)
// or mark file(s) for deletion
form.removeFiles("yourFileField2", "demo_xzihx0w.png")
// validate and submit (internally it calls $app.dao().saveRecord(record) in a transaction)
form.submit()
Intercept record before create API hook
onRecordBeforeCreateRequest((e) => {
if (e.httpContext.get("admin")) {
return null // ignore for admins
}
// overwrite the submitted "active" field value to false
e.record.set("active", false)
// or you can also prevent the create event by returning an error, eg.:
if (e.record.get("status") != "pending") {
throw new BadRequestError("status must be pending")
}
}, "articles")
Update existing record
Update record WITHOUT data validations
const record = $app.dao().findRecordById("articles", "RECORD_ID")
// set individual fields
// or bulk load with record.load({...})
record.set("title", "Lorem ipsum")
record.set("active", true)
record.set("someOtherField", 123)
$app.dao().saveRecord(record)
Update record WITH data validations
const record = $app.dao().findRecordById("articles", "RECORD_ID")
const form = new RecordUpsertForm($app, record)
// or form.loadRequest(request, "")
form.loadData({
"title": "Lorem ipsum",
"active": true,
"someOtherField": 123,
})
// validate and submit (internally it calls $app.dao().saveRecord(record) in a transaction)
form.submit();
Intercept record before update API hook
onRecordBeforeUpdateRequest((e) => {
if (e.httpContext.get("admin")) {
return null // ignore for admins
}
// overwrite the submitted "active" field value to false
e.record.set("active", false)
// or you can also prevent the create event by returning an error, eg.:
if (e.record.get("status") != "pending") {
throw new BadRequestError("status must be pending")
}
}, "articles")
Delete record
const record = $app.dao().findRecordById("articles", "RECORD_ID")
$app.dao().deleteRecord(record)
Transaction
const titles = ["title1", "title2", "title3"]
const collection = $app.dao().findCollectionByNameOrId("articles")
$app.dao().runInTransaction((txDao) => {
// create new record for each title
for (let title of titles) {
const record = new Record(collection)
record.set("title", title)
txDao.saveRecord(record)
}
})
Programmatically expanding relations
To expand record relations programmatically you can use the
$app.dao().expandRecord(record, expands, customFetchFunc)
or
$app.dao().expandRecords(records, expands, customFetchFunc)
methods.
Once loaded, you can access the expanded relations via
record.expandedOne(relName)
or
record.expandedAll(relName)
methods.
For example:
const record = $app.dao().findFirstRecordByData("articles", "slug", "lorem-ipsum")
// expand the "author" and "categories" relations
$app.dao().expandRecord(record, ["author", "categories"], null)
// print the expanded records
console.log(record.expandedOne("author"))
console.log(record.expandedAll("categories"))
Check if record can be accessed
To check whether a custom client request or user can access a single record, you can use the
$app.dao().canAccessRecord(record, requestInfo, rule)
method.
For example:
// allow access to the article with the specified slug
// only if the current client request satisfy the articles view rule
routerAdd("get", "/articles/:slug", (c) => {
const info = $apis.requestInfo(c)
const slug = c.pathParam("slug")
const record = $app.dao().findFirstRecordByData("articles", "slug", slug)
const canAccess = $app.dao().canAccessRecord(record, info, record.collection().viewRule)
if (!canAccess) {
throw new ForbiddenError()
}
return c.json(200, record)
})