3.6 KiB
PB 0.39.1 JS Migration Quirks
Collected during SPQ-v2 Phase 4 implementation (2026-07-07).
API Rule Validation Ordering Bug
Symptom: listRule / viewRule / createRule that reference custom fields fail with invalid left operand "foo" - unknown field "foo" even when the field IS in the schema.
Root cause: PB 0.39 validates API rules against its internal schema snapshot at rule-set time. If you create a collection AND set rules in the same migrate() call, the rules validator is running against the PRE-COMMIT snapshot (which doesn't have the new fields yet).
What does NOT work (all three tried, all failed):
- Saving with empty rules, then setting rules on the same object +
app.save()again — FAILS - Saving with empty rules, then
app.findCollectionByNameOrId(), setting rules on fetched object +app.save()— FAILS - Separate migration file (M27 — after M25/M26 committed) — STILL FAILS
The rules validator always runs against the pre-commit snapshot. The only workaround for single-shop deployments: use empty rules ('') and rely on frontend filtering + createRule: null for write protection on server-side hook-only collections.
When you need scoped rules: Use the PocketBase admin UI after deployment, not JS migrations.
Missing Field Constructors
| Wanted | Use Instead |
|---|---|
DateTimeField |
TextField — store ISO strings |
DateField |
TextField — store ISO strings |
Available constructors (confirmed working in PB 0.39.1):
TextFieldEmailFieldSelectFieldJSONFieldBoolFieldNumberField(likely, not confirmed)
JS Runtime Support
PB 0.39.1 uses Goja (Go-based ES runtime). Confirmed working:
- ✅
const,let,var - ✅ Template literals
`hello ${world}` - ✅ Arrow functions
(x) => x + 1 - ✅
Object.keys(),Array.forEach(),JSON.stringify() - ✅
new Collection({ name, type, schema: [...] }) - ✅
new Record(collection, data)
Hook Event Names
| Correct API (PB 0.39) | Names that DON'T exist |
|---|---|
app.onRecordCreateRequest((e) => { ... }) |
— |
app.onRecordUpdateRequest((e) => { ... }) |
onRecordBeforeUpdateRequest, onRecordAfterUpdateRequest |
app.onRecordDeleteRequest((e) => { ... }) |
— |
Only the three "Request" hooks exist — there are no "Before"/"After" variants.
Record Access in Hooks
e.record— the record being saved (in update hooks, this is the NEW data)e.oldRecord— available in update hooks, the current DB state before the updatee.dao— data access object for querying other collectionse.httpContext— access to the HTTP request (query params, headers)
Creating Records Inside Hooks
var collection = app.findCollectionByNameOrId('myCollection');
var record = new Record(collection, {
field1: 'value1',
field2: 'value2',
});
e.dao.saveRecord(record);
Public Share-Token Access Pattern
To detect if a request is using the public share-token path (vs. authenticated API):
var shareToken = e.httpContext?.request?.query?.get('shareToken') || '';
if (shareToken) {
// This is a public (unauthenticated) update via share link
// Enforce expiry, restrict fields, etc.
}
Mailer API (for sending email in hooks)
var mailer = app.getMailer();
await mailer.send({
to: [{ address: 'user@example.com', name: 'Name' }],
subject: 'Subject',
html: '<p>HTML body</p>',
});
Requires SMTP env vars set on the PB container (PB will crash on startup without valid PB_SMTP_* vars once mailer is used).