- ERPNext Client Scripts - Error Handling
- This skill covers error handling patterns for Client Scripts. For syntax, see
- erpnext-syntax-clientscripts
- . For implementation workflows, see
- erpnext-impl-clientscripts
- .
- Version
-
- v14/v15/v16 compatible
- Main Decision: How to Handle the Error?
- ┌─────────────────────────────────────────────────────────────────────────┐
- │ WHAT TYPE OF ERROR ARE YOU HANDLING? │
- ├─────────────────────────────────────────────────────────────────────────┤
- │ │
- │ ► Validation error (must prevent save)? │
- │ └─► frappe.throw() in validate event │
- │ │
- │ ► Warning (inform user, allow continue)? │
- │ └─► frappe.msgprint() with indicator │
- │ │
- │ ► Server call might fail? │
- │ └─► try/catch with async/await OR callback error handling │
- │ │
- │ ► Field value invalid (not blocking)? │
- │ └─► frm.set_intro() or field description │
- │ │
- │ ► Need to debug/trace? │
- │ └─► console.log/warn/error + frappe.show_alert for dev │
- │ │
- │ ► Unexpected error in any code? │
- │ └─► Wrap in try/catch, log error, show user-friendly message │
- │ │
- └─────────────────────────────────────────────────────────────────────────┘
- Error Feedback Methods
- Quick Reference
- Method
- Blocks Save?
- User Action
- Use For
- frappe.throw()
- ✅ YES
- Must dismiss
- Validation errors
- frappe.msgprint()
- ❌ NO
- Must dismiss
- Important info/warnings
- frappe.show_alert()
- ❌ NO
- Auto-dismiss
- Success/info feedback
- frm.set_intro()
- ❌ NO
- None
- Form-level warnings
- frm.dashboard.set_headline()
- ❌ NO
- None
- Status indicators
- console.error()
- ❌ NO
- None
- Debugging only
- frappe.throw() - Blocking Error
- // Basic throw - stops execution, prevents save
- frappe
- .
- throw
- (
- __
- (
- 'Customer is required'
- )
- )
- ;
- // With title
- frappe
- .
- throw
- (
- {
- title
- :
- __
- (
- 'Validation Error'
- )
- ,
- message
- :
- __
- (
- 'Amount cannot be negative'
- )
- }
- )
- ;
- // With indicator color
- frappe
- .
- throw
- (
- {
- message
- :
- __
- (
- 'Credit limit exceeded'
- )
- ,
- indicator
- :
- 'red'
- }
- )
- ;
- CRITICAL
- Only use
frappe.throw()
in
validate
event to prevent save. Using it elsewhere stops script execution but doesn't prevent form actions.
frappe.msgprint() - Non-Blocking Alert
// Simple message
frappe
.
msgprint
(
__
(
'Document will be processed'
)
)
;
// With title and indicator
frappe
.
msgprint
(
{
title
:
__
(
'Warning'
)
,
message
:
__
(
'Stock is running low'
)
,
indicator
:
'orange'
}
)
;
// With primary action button
frappe
.
msgprint
(
{
title
:
__
(
'Confirm Action'
)
,
message
:
__
(
'This will archive 50 records. Continue?'
)
,
primary_action
:
{
label
:
__
(
'Yes, Archive'
)
,
action
:
(
)
=>
{
// perform action
frappe
.
hide_msgprint
(
)
;
}
}
}
)
;
frappe.show_alert() - Toast Notification
// Success (green, auto-dismiss)
frappe
.
show_alert
(
{
message
:
__
(
'Saved successfully'
)
,
indicator
:
'green'
}
,
3
)
;
// 3 seconds
// Warning (orange)
frappe
.
show_alert
(
{
message
:
__
(
'Some items are out of stock'
)
,
indicator
:
'orange'
}
,
5
)
;
// Error (red)
frappe
.
show_alert
(
{
message
:
__
(
'Failed to fetch data'
)
,
indicator
:
'red'
}
,
5
)
;
Error Handling Patterns
Pattern 1: Synchronous Validation
frappe
.
ui
.
form
.
on
(
'Sales Order'
,
{
validate
(
frm
)
{
// Multiple validations - collect errors
let
errors
=
[
]
;
if
(
!
frm
.
doc
.
customer
)
{
errors
.
push
(
__
(
'Customer is required'
)
)
;
}
if
(
frm
.
doc
.
grand_total
<=
0
)
{
errors
.
push
(
__
(
'Total must be greater than zero'
)
)
;
}
if
(
!
frm
.
doc
.
items
||
frm
.
doc
.
items
.
length
===
0
)
{
errors
.
push
(
__
(
'At least one item is required'
)
)
;
}
// Throw all errors at once
if
(
errors
.
length
0 ) { frappe . throw ( { title : __ ( 'Validation Errors' ) , message : errors . join ( '
' ) } ) ; } } } ) ; Pattern 2: Async Server Call with Error Handling frappe . ui . form . on ( 'Sales Order' , { async customer ( frm ) { if ( ! frm . doc . customer ) return ; try { let r = await frappe . call ( { method : 'myapp.api.get_customer_details' , args : { customer : frm . doc . customer } } ) ; if ( r . message ) { frm . set_value ( 'credit_limit' , r . message . credit_limit ) ; } } catch ( error ) { console . error ( 'Failed to fetch customer:' , error ) ; frappe . show_alert ( { message : __ ( 'Could not load customer details' ) , indicator : 'red' } , 5 ) ; // Don't throw - allow user to continue } } } ) ; Pattern 3: Async Validation with Server Check frappe . ui . form . on ( 'Sales Order' , { async validate ( frm ) { // Server-side validation try { let r = await frappe . call ( { method : 'myapp.api.validate_order' , args : { customer : frm . doc . customer , total : frm . doc . grand_total } } ) ; if ( r . message && ! r . message . valid ) { frappe . throw ( { title : __ ( 'Validation Failed' ) , message : r . message . error } ) ; } } catch ( error ) { // Server error - decide: block or allow? console . error ( 'Validation call failed:' , error ) ; // Option 1: Block save on server error (safer) frappe . throw ( __ ( 'Could not validate. Please try again.' ) ) ; // Option 2: Allow save with warning (use with caution) // frappe.show_alert({ // message: __('Validation skipped due to server error'), // indicator: 'orange' // }, 5); } } } ) ; Pattern 4: frappe.call Callback Error Handling // When not using async/await frappe . call ( { method : 'myapp.api.process_data' , args : { doc_name : frm . doc . name } , freeze : true , freeze_message : __ ( 'Processing...' ) , callback : ( r ) => { if ( r . message ) { frappe . show_alert ( { message : __ ( 'Processing complete' ) , indicator : 'green' } ) ; frm . reload_doc ( ) ; } } , error : ( r ) => { // Server returned error (4xx, 5xx) console . error ( 'API Error:' , r ) ; frappe . msgprint ( { title : __ ( 'Error' ) , message : __ ( 'Processing failed. Please try again.' ) , indicator : 'red' } ) ; } } ) ; Pattern 5: Child Table Validation frappe . ui . form . on ( 'Sales Invoice' , { validate ( frm ) { let errors = [ ] ; ( frm . doc . items || [ ] ) . forEach ( ( row , idx ) => { if ( ! row . item_code ) { errors . push ( __ ( 'Row {0}: Item is required' , [ idx + 1 ] ) ) ; } if ( row . qty <= 0 ) { errors . push ( __ ( 'Row {0}: Quantity must be positive' , [ idx + 1 ] ) ) ; } if ( row . rate < 0 ) { errors . push ( __ ( 'Row {0}: Rate cannot be negative' , [ idx + 1 ] ) ) ; } } ) ; if ( errors . length0 ) { frappe . throw ( { title : __ ( 'Item Errors' ) , message : errors . join ( '
' ) } ) ; } } } ) ; Pattern 6: Graceful Degradation frappe . ui . form . on ( 'Sales Order' , { async refresh ( frm ) { // Try to load extra data, but don't fail if unavailable try { let stock = await frappe . call ( { method : 'myapp.api.get_stock_summary' , args : { items : frm . doc . items . map ( r => r . item_code ) } } ) ; if ( stock . message ) { render_stock_dashboard ( frm , stock . message ) ; } } catch ( error ) { // Log but don't disturb user console . warn ( 'Stock dashboard unavailable:' , error ) ; // Optionally show subtle indicator frm . dashboard . set_headline ( __ ( 'Stock info unavailable' ) , 'orange' ) ; } } } ) ; See : references/patterns.md for more error handling patterns. Debugging Techniques Console Logging // Development debugging frappe . ui . form . on ( 'Sales Order' , { customer ( frm ) { console . log ( 'Customer changed:' , frm . doc . customer ) ; console . log ( 'Full doc:' , JSON . parse ( JSON . stringify ( frm . doc ) ) ) ; // Trace child table console . table ( frm . doc . items ) ; } } ) ; Conditional Debugging // Only log in development const DEBUG = frappe . boot . developer_mode ; function debugLog ( ... args ) { if ( DEBUG ) { console . log ( '[MyApp]' , ... args ) ; } } frappe . ui . form . on ( 'Sales Order' , { validate ( frm ) { debugLog ( 'Validating:' , frm . doc . name ) ; // validation logic } } ) ; Error Stack Traces try { riskyOperation ( ) ; } catch ( error ) { console . error ( 'Error details:' , { message : error . message , stack : error . stack , doc : frm . doc . name } ) ; // User-friendly message (no technical details) frappe . msgprint ( { title : __ ( 'Error' ) , message : __ ( 'An unexpected error occurred. Please contact support.' ) , indicator : 'red' } ) ; } Critical Rules ✅ ALWAYS Wrap async calls in try/catch - Uncaught Promise rejections crash silently Use __() for error messages - All user-facing text must be translatable Log errors to console - Helps debugging without exposing to users Collect multiple validation errors - Don't throw on first error Provide actionable error messages - Tell user how to fix it ❌ NEVER Don't expose technical errors to users - Catch and translate Don't use frappe.throw() outside validate - It stops execution but doesn't prevent save Don't ignore server call failures - Always handle error callback Don't use alert() or confirm() - Use frappe methods instead Don't leave console.log in production - Use conditional debugging Quick Reference: Error Message Quality // ❌ BAD - Technical, not actionable frappe . throw ( 'NullPointerException in line 42' ) ; frappe . throw ( 'Query failed' ) ; frappe . throw ( 'Error' ) ; // ✅ GOOD - Clear, actionable frappe . throw ( __ ( 'Please select a customer before adding items' ) ) ; frappe . throw ( __ ( 'Amount {0} exceeds credit limit of {1}' , [ amount , limit ] ) ) ; frappe . throw ( __ ( 'Could not save. Please check your internet connection and try again.' ) ) ; Reference Files File Contents references/patterns.md Complete error handling patterns references/examples.md Full working examples references/anti-patterns.md Common mistakes to avoid See Also erpnext-syntax-clientscripts - Client Script syntax erpnext-impl-clientscripts - Implementation workflows erpnext-errors-serverscripts - Server-side error handling
erpnext-errors-clientscripts
安装
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-errors-clientscripts