Skip to content
AI & Agents
Skill

/frappe-errors-clientscripts

Use 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

From plugin
frappe-claude-skill-package
17861 skills
Install
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-errors-clientscripts --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frappe-errors-clientscripts

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use 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

SKILL.md

frappe-errors-clientscripts.SKILL.md
name: frappe-errors-clientscripts
description: >
  Use 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 error diagnosis flowchart and debug tools for v14/v15/v16.
  Keywords: client script error, TypeError, frappe.call, async await,, Cannot read properties of undefined, TypeError, browser console error, script not running, form not updating.
  cur_frm, field not found, child table, CSRF, permission denied.
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
  author: OpenAEC-Foundation
  version: "2.0"

Client Script Errors — Diagnosis and Resolution

Cross-refs: `frappe-syntax-clientscripts` (syntax), `frappe-impl-clientscripts` (workflows), `frappe-errors-serverscripts` (server-side).

---

Error Diagnosis Flowchart

ERROR IN CLIENT SCRIPT
│
├─► TypeError: Cannot read properties of undefined
│   ├─► "frm.doc.fieldname" → Field does not exist on DocType
│   ├─► "r.message.value" → Server returned null/error
│   └─► "row.fieldname" in child table → Row not fetched correctly
│
├─► frappe.call fails silently
│   ├─► Missing error callback → Add error handler
│   ├─► 403 Forbidden → Method not whitelisted (@frappe.whitelist)
│   ├─► 417 Expectation Failed → Server-side frappe.throw()
│   └─► 401 Unauthorized → Session expired or CSRF token invalid
│
├─► Uncaught (in promise) → Missing try/catch on async frappe.call
│
├─► Field appears blank after set_value → Timing issue (setup vs refresh)
│
├─► cur_frm is undefined → Using cur_frm in list/report context
│
└─► frappe.throw() does not prevent save → Used outside validate event

---

Error Message → Cause → Fix Table

| Error Message | Cause | Fix | |---------------|-------|-----| | `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 | | `TypeError: frm.set_value is not a function` | Using `cur_frm` shortcut that is undefined | ALWAYS use the `frm` parameter from event handler | | `Uncaught (in promise)` | Unhandled async rejection from frappe.call | ALWAYS wrap async calls in try/catch | | `CSRFTokenError` / `403 with CSRF` | Token mismatch after session timeout | ALWAYS use `frappe.call()` (handles CSRF automatically) | | `Not permitted` / 403 on frappe.call | Server method missing `@frappe.whitelist()` | ALWAYS add `@frappe.whitelist()` decorator to API methods | | `frappe.throw() not preventing save` | `frappe.throw()` used outside `validate` event | ALWAYS use `frappe.throw()` only in `validate` | | `field not found: xyz` in set_query | Fieldname typo or field not in child table | Verify exact fieldname against DocType definition | | `row.item_code is undefined` | Accessing child row wrong — `locals` not synced | Use `frappe.get_doc(cdt, cdn)` in child table events | | `frm.set_value not working` | Called in `setup` before form fully loaded | Move field-setting logic to `refresh` event | | `Maximum call stack exceeded` | Circular trigger — field change fires own handler | Use `frm.flags` guard to break recursion |

---

Critical Error Patterns

1. cur_frm vs frm: The #1 Beginner Mistake

// ❌ WRONG — cur_frm is undefined in many contexts
frappe.ui.form.on('Sales Order', {
    customer(frm) {
        cur_frm.set_value('territory', 'Default');  // BREAKS in list view
    }
});

// ✅ CORRECT — ALWAYS use the frm parameter
frappe.ui.form.on('Sales Order', {
    customer(frm) {
        frm.set_value('territory', 'Default');
    }
});

**Rule**: NEVER use `cur_frm`. ALWAYS use the `frm` parameter passed to every event handler.

2. Async/Await: Silent Failure Without try/catch

// ❌ WRONG — Unhandled rejection crashes silently
frappe.ui.form.on('Sales Order', {
    async customer(frm) {
        let r = await frappe.call({
            method: 'myapp.api.get_data',
            args: { customer: frm.doc.customer }
        });
        frm.set_value('credit_limit', r.message.limit);  // r.message may be null
    }
});

// ✅ CORRECT — try/catch with null check
frappe.ui.form.on('Sales Order', {
    async customer(frm) {
        if (!frm.doc.customer) return;
        try {
            let r = await frappe.call({
                method: 'myapp.api.get_data',
                args: { customer: frm.doc.customer }
            });
            if (r.message) {
                frm.set_value('credit_limit', r.message.limit || 0);
            }
        } catch (error) {
            console.error('Customer fetch failed:', error);
            frappe.show_alert({
                message: __('Could not load customer details'),
                indicator: 'red'
            }, 5);
        }
    }
});

3. Child Table Access: Wrong Pattern

// ❌ WRONG — frm.doc.items[0] may not reflect latest state
frappe.ui.form.on('Sales Order Item', {
    item_code(frm, cdt, cdn) {
        let row = frm.doc.items.find(r => r.name === cdn);  // fragile
        row.rate = 100;  // Does not trigger UI refresh
    }
});

// ✅ CORRECT — Use frappe.get_doc and frappe.model.set_value
frappe.ui.form.on('Sales Order Item', {
    item_code(frm, cdt, cdn) {
        let row = frappe.get_doc(cdt, cdn);
        if (!row.item_code) return;
        frappe.model.set_value(cdt, cdn, 'rate', 100);  // Triggers refresh
    }
});

4. Timing: setup vs refresh

// ❌ WRONG — set_value in setup, form not ready
frappe.ui.form.on('Sales Order', {
    setup(frm) {
        frm.set_value('company', 'My Company');  // May not work
    }
});

// ✅ CORRECT — set_query in setup, set_value in refresh/onload
frappe.ui.form.on('Sales Order', {
    setup(frm) {
        // Filters belong in setup
        frm.se
Read more
Ships withfrappe-claude-skill-package

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Get the whole plugin
Stats
178
Stars
53
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
8mo ago
Created
14d ago
Added

Repo: Impertio-Studio/Frappe_Claude_Skill_Package