# 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):** 1. Saving with empty rules, then setting rules on the same object + `app.save()` again — FAILS 2. Saving with empty rules, then `app.findCollectionByNameOrId()`, setting rules on fetched object + `app.save()` — FAILS 3. 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): - `TextField` - `EmailField` - `SelectField` - `JSONField` - `BoolField` - `NumberField` (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 update - `e.dao` — data access object for querying other collections - `e.httpContext` — access to the HTTP request (query params, headers) ## Creating Records Inside Hooks ```js 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): ```js 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) ```js var mailer = app.getMailer(); await mailer.send({ to: [{ address: 'user@example.com', name: 'Name' }], subject: 'Subject', html: '

HTML body

', }); ``` Requires SMTP env vars set on the PB container (PB will crash on startup without valid `PB_SMTP_*` vars once mailer is used).