$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-errors-clientscriptsUse when debugging or preventing errors in Frappe Client Scripts. Prevents TypeError, frappe.call failures, async/await mistakes, cur_frm vs frm confusion, field not found, child table access errors, timing issues, CSRF token errors, and permission denied on frappe.call. Covers e
| 1 | # Client Script Errors — Diagnosis and Resolution |
| 2 | |
| 3 | Cross-refs: `frappe-syntax-clientscripts` (syntax), `frappe-impl-clientscripts` (workflows), `frappe-errors-serverscripts` (server-side). |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## Error Diagnosis Flowchart |
| 8 | |
| 9 | ``` |
| 10 | ERROR IN CLIENT SCRIPT |
| 11 | │ |
| 12 | ├─► TypeError: Cannot read properties of undefined |
| 13 | │ ├─► "frm.doc.fieldname" → Field does not exist on DocType |
| 14 | │ ├─► "r.message.value" → Server returned null/error |
| 15 | │ └─► "row.fieldname" in child table → Row not fetched correctly |
| 16 | │ |
| 17 | ├─► frappe.call fails silently |
| 18 | │ ├─► Missing error callback → Add error handler |
| 19 | │ ├─► 403 Forbidden → Method not whitelisted (@frappe.whitelist) |
| 20 | │ ├─► 417 Expectation Failed → Server-side frappe.throw() |
| 21 | │ └─► 401 Unauthorized → Session expired or CSRF token invalid |
| 22 | │ |
| 23 | ├─► Uncaught (in promise) → Missing try/catch on async frappe.call |
| 24 | │ |
| 25 | ├─► Field appears blank after set_value → Timing issue (setup vs refresh) |
| 26 | │ |
| 27 | ├─► cur_frm is undefined → Using cur_frm in list/report context |
| 28 | │ |
| 29 | └─► frappe.throw() does not prevent save → Used outside validate event |
| 30 | ``` |
| 31 | |
| 32 | --- |
| 33 | |
| 34 | ## Error Message → Cause → Fix Table |
| 35 | |
| 36 | | Error Message | Cause | Fix | |
| 37 | |---------------|-------|-----| |
| 38 | | `TypeError: Cannot read properties of undefined (reading 'fieldname')` | Field does not exist on DocType or doc not loaded | ALWAYS check `frm.doc` exists before accessing fields | |
| 39 | | `TypeError: frm.set_value is not a function` | Using `cur_frm` shortcut that is undefined | ALWAYS use the `frm` parameter from event handler | |
| 40 | | `Uncaught (in promise)` | Unhandled async rejection from frappe.call | ALWAYS wrap async calls in try/catch | |
| 41 | | `CSRFTokenError` / `403 with CSRF` | Token mismatch after session timeout | ALWAYS use `frappe.call()` (handles CSRF automatically) | |
| 42 | | `Not permitted` / 403 on frappe.call | Server method missing `@frappe.whitelist()` | ALWAYS add `@frappe.whitelist()` decorator to API methods | |
| 43 | | `frappe.throw() not preventing save` | `frappe.throw()` used outside `validate` event | ALWAYS use `frappe.throw()` only in `validate` | |
| 44 | | `field not found: xyz` in set_query | Fieldname typo or field not in child table | Verify exact fieldname against DocType definition | |
| 45 | | `row.item_code is undefined` | Accessing child row wrong — `locals` not synced | Use `frappe.get_doc(cdt, cdn)` in child table events | |
| 46 | | `frm.set_value not working` | Called in `setup` before form fully loaded | Move field-setting logic to `refresh` event | |
| 47 | | `Maximum call stack exceeded` | Circular trigger — field change fires own handler | Use `frm.flags` guard to break recursion | |
| 48 | |
| 49 | --- |
| 50 | |
| 51 | ## Critical Error Patterns |
| 52 | |
| 53 | ### 1. cur_frm vs frm: The #1 Beginner Mistake |
| 54 | |
| 55 | ```javascript |
| 56 | // ❌ WRONG — cur_frm is undefined in many contexts |
| 57 | frappe.ui.form.on('Sales Order', { |
| 58 | customer(frm) { |
| 59 | cur_frm.set_value('territory', 'Default'); // BREAKS in list view |
| 60 | } |
| 61 | }); |
| 62 | |
| 63 | // ✅ CORRECT — ALWAYS use the frm parameter |
| 64 | frappe.ui.form.on('Sales Order', { |
| 65 | customer(frm) { |
| 66 | frm.set_value('territory', 'Default'); |
| 67 | } |
| 68 | }); |
| 69 | ``` |
| 70 | |
| 71 | **Rule**: NEVER use `cur_frm`. ALWAYS use the `frm` parameter passed to every event handler. |
| 72 | |
| 73 | ### 2. Async/Await: Silent Failure Without try/catch |
| 74 | |
| 75 | ```javascript |
| 76 | // ❌ WRONG — Unhandled rejection crashes silently |
| 77 | frappe.ui.form.on('Sales Order', { |
| 78 | async customer(frm) { |
| 79 | let r = await frappe.call({ |
| 80 | method: 'myapp.api.get_data', |
| 81 | args: { customer: frm.doc.customer } |
| 82 | }); |
| 83 | frm.set_value('credit_limit', r.message.limit); // r.message may be null |
| 84 | } |
| 85 | }); |
| 86 | |
| 87 | // ✅ CORRECT — try/catch with null check |
| 88 | frappe.ui.form.on('Sales Order', { |
| 89 | async customer(frm) { |
| 90 | if (!frm.doc.customer) return; |
| 91 | try { |
| 92 | let r = await frappe.call({ |
| 93 | method: 'myapp.api.get_data', |
| 94 | args: { customer: frm.doc.customer } |
| 95 | }); |
| 96 | if (r.message) { |
| 97 | frm.set_value('credit_limit', r.message.limit || 0); |
| 98 | } |
| 99 | } catch (error) { |
| 100 | console.error('Customer fetch failed:', error); |
| 101 | frappe.show_alert({ |
| 102 | message: __('Could not load customer details'), |
| 103 | indicator: 'red' |
| 104 | }, 5); |
| 105 | } |
| 106 | } |
| 107 | }); |
| 108 | ``` |
| 109 | |
| 110 | ### 3. Child T |