Creating records
Basic creation
const collection = $app.findCollectionByNameOrId("posts")
const record = new Record(collection, {
title: "Getting Started with PocketBase",
content: "PocketBase is an open source backend...",
author: "user_id_here",
status: "draft",
})
$app.save(record)
Using set method
const collection = $app.findCollectionByNameOrId("posts")
const record = new Record(collection)
record.set("title", "My New Post")
record.set("content", "This is the content")
record.set("author", authRecord.id)
record.set("status", "published")
$app.save(record)
With file uploads
onRecordCreateRequest((e) => {
if (e.collection.name !== "posts") {
return e.next()
}
const form = e.requestInfo().data
const file = form.featured_image
if (file) {
// File is automatically handled by PocketBase
// Just ensure your collection has a file field named "featured_image"
}
return e.next()
}, "posts")
Reading records
Find by ID
try {
const record = $app.findRecordById("posts", "RECORD_ID")
console.log(record.getString("title"))
} catch (err) {
console.log("Record not found")
}
Find by field value
// Find first match
const user = $app.findFirstRecordByData("users", "email", "test@example.com")
// Find all matches
const records = arrayOf(new Record())
$app.findAllRecordsByData("posts", "status", "published", records)
for (let record of records) {
console.log(record.getString("title"))
}
Using query builder
const records = arrayOf(new Record())
$app.recordQuery("posts")
.andWhere($dbx.hashExp({
"status": "published",
}))
.andWhere($dbx.like("title", "JavaScript"))
.orderBy("created DESC")
.limit(20)
.all(records)
console.log("Found", records.length, "posts")
Query with relations
const records = arrayOf(new Record())
$app.recordQuery("posts")
.andWhere($dbx.hashExp({"status": "published"}))
.orderBy("created DESC")
.limit(10)
.all(records)
// Expand author relation
for (let record of records) {
const authorId = record.getString("author")
const author = $app.findRecordById("users", authorId)
console.log(record.getString("title"), "by", author.getString("username"))
}
Updating records
Update fields
const record = $app.findRecordById("posts", "RECORD_ID")
record.set("title", "Updated Title")
record.set("content", "Updated content here")
record.set("status", "published")
$app.save(record)
Partial updates
const record = $app.findRecordById("posts", "RECORD_ID")
// Only update specific fields
record.set("view_count", record.getInt("view_count") + 1)
$app.save(record)
Update with validation
const record = $app.findRecordById("posts", "RECORD_ID")
const newTitle = "New Title"
if (newTitle.length < 3) {
throw new BadRequestError("Title must be at least 3 characters")
}
record.set("title", newTitle)
$app.save(record)
Deleting records
Delete single record
const record = $app.findRecordById("posts", "RECORD_ID")
$app.delete(record)
Delete with cascade
// When you delete a record, related records are handled
// based on the cascadeDelete setting in relation fields
const user = $app.findRecordById("users", "USER_ID")
// If posts have a relation to users with cascadeDelete: true,
// all posts by this user will be deleted automatically
$app.delete(user)
Bulk delete
const records = arrayOf(new Record())
// Find all draft posts older than 30 days
const cutoff = new DateTime()
cutoff.addDate(0, 0, -30)
$app.recordQuery("posts")
.andWhere($dbx.hashExp({"status": "draft"}))
.andWhere($dbx.exp("created < {:cutoff}", {cutoff: cutoff.string()}))
.all(records)
for (let record of records) {
$app.delete(record)
}
console.log("Deleted", records.length, "old drafts")
Accessing field values
Type-safe getters
const record = $app.findRecordById("posts", "RECORD_ID")
// String fields
const title = record.getString("title")
const content = record.getString("content")
// Number fields
const viewCount = record.getInt("view_count")
const price = record.getFloat("price")
// Boolean fields
const featured = record.getBool("featured")
// Date fields
const publishedAt = record.getDateTime("published_at")
// JSON fields
const metadata = record.get("metadata")
Working with arrays
const record = $app.findRecordById("posts", "RECORD_ID")
// Select fields (arrays)
const tags = record.get("tags") || []
for (let tag of tags) {
console.log("Tag:", tag)
}
// Relation fields (arrays of IDs)
const categoryIds = record.get("categories") || []
for (let categoryId of categoryIds) {
const category = $app.findRecordById("categories", categoryId)
console.log("Category:", category.getString("name"))
}
File handling
Get file URLs
const record = $app.findRecordById("posts", "RECORD_ID")
const filename = record.getString("featured_image")
if (filename) {
const url = $app.fileUrl(record, filename)
console.log("Image URL:", url)
// Get thumbnail URL
const thumbUrl = $app.fileUrl(record, filename, "200x200")
console.log("Thumbnail URL:", thumbUrl)
}
Upload files programmatically
const collection = $app.findCollectionByNameOrId("posts")
const record = new Record(collection)
record.set("title", "Post with Image")
// Create file from local path
const file = $filesystem.fileFromPath("/path/to/image.jpg")
record.set("featured_image", file)
$app.save(record)
Download and attach external files
const collection = $app.findCollectionByNameOrId("posts")
const record = new Record(collection)
record.set("title", "Post with External Image")
// Download file from URL
const file = $filesystem.fileFromURL("https://example.com/image.jpg", 30)
record.set("featured_image", file)
$app.save(record)
Working with auth records
Create user
const collection = $app.findCollectionByNameOrId("users")
const user = new Record(collection)
user.set("email", "newuser@example.com")
user.set("username", "newuser")
user.setPassword("securePassword123")
user.set("emailVisibility", true)
user.set("verified", false)
$app.save(user)
// Send verification email
$mails.sendRecordVerification($app, user)
Update password
const user = $app.findAuthRecordByEmail("users", "user@example.com")
user.setPassword("newSecurePassword456")
$app.save(user)
Verify email
const user = $app.findAuthRecordByEmail("users", "user@example.com")
user.set("verified", true)
$app.save(user)
Validation
Manual validation
onRecordCreate((e) => {
if (e.collection.name !== "posts") {
return e.next()
}
const record = e.record
// Custom validation
const title = record.getString("title")
if (title.includes("spam")) {
throw new BadRequestError("Title contains forbidden words")
}
// Check for duplicates
const existing = $app.findFirstRecordByData(
"posts",
"slug",
record.getString("slug")
)
if (existing) {
throw new BadRequestError("A post with this slug already exists")
}
return e.next()
}, "posts")
Using validation errors
routerAdd("POST", "/api/posts/validate", (e) => {
const data = e.requestInfo().data
const errors = {}
if (!data.title || data.title.length < 3) {
errors.title = new ValidationError("validation_min_length", "Title is too short")
}
if (!data.content || data.content.length < 10) {
errors.content = new ValidationError("validation_required", "Content is required")
}
if (Object.keys(errors).length > 0) {
throw new BadRequestError("Validation failed", errors)
}
return e.json(200, {valid: true})
})
Examples
Increment view counter
onRecordViewRequest((e) => {
if (e.collection.name !== "posts") {
return e.next()
}
// Increment view count
const record = e.record
const currentCount = record.getInt("view_count") || 0
record.set("view_count", currentCount + 1)
$app.save(record)
return e.next()
}, "posts")
Auto-generate slug
onRecordCreate((e) => {
if (e.collection.name !== "posts") {
return e.next()
}
const record = e.record
const title = record.getString("title")
// Generate slug from title
const slug = title
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
record.set("slug", slug)
return e.next()
}, "posts")
Copy record
routerAdd("POST", "/api/posts/:id/duplicate", (e) => {
const id = e.request.pathValue("id")
const original = $app.findRecordById("posts", id)
const collection = $app.findCollectionByNameOrId("posts")
const copy = new Record(collection)
// Copy all fields except ID and timestamps
copy.set("title", original.getString("title") + " (Copy)")
copy.set("content", original.getString("content"))
copy.set("author", original.getString("author"))
copy.set("status", "draft")
$app.save(copy)
return e.json(200, copy)
}, $apis.requireAuth())
Batch create
routerAdd("POST", "/api/posts/batch", (e) => {
const data = e.requestInfo().data
const posts = data.posts || []
const collection = $app.findCollectionByNameOrId("posts")
const created = []
$app.runInTransaction((txApp) => {
for (let postData of posts) {
const record = new Record(collection)
record.set("title", postData.title)
record.set("content", postData.content)
record.set("author", e.requestInfo().auth.id)
record.set("status", "draft")
txApp.save(record)
created.push(record)
}
return null
})
return e.json(200, {
created: created.length,
records: created,
})
}, $apis.requireAuth())