← Back to list

erpnext-errors-serverscripts
by OpenAEC-Foundation
28 deterministic Claude AI skills for flawless ERPNext/Frappe v14-16 development. Agent Skills standard compliant.
⭐ 1🍴 1📅 Jan 23, 2026
SKILL.md
name: erpnext-errors-serverscripts description: "Error handling patterns for ERPNext Server Scripts. Use when handling sandbox errors, frappe.throw usage, validation in server scripts, and debugging. V14/V15/V16 compatible. Triggers: server script error, frappe.throw, sandbox error, validation error, debugging server script."
ERPNext Server Scripts - Error Handling
This skill covers error handling patterns for Server Scripts. For syntax, see erpnext-syntax-serverscripts. For implementation workflows, see erpnext-impl-serverscripts.
Version: v14/v15/v16 compatible
CRITICAL: Sandbox Limitations for Error Handling
┌─────────────────────────────────────────────────────────────────────┐
│ ⚠️ SANDBOX RESTRICTIONS AFFECT ERROR HANDLING │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ NO try/except blocks (blocked in RestrictedPython) │
│ ❌ NO raise statements (use frappe.throw instead) │
│ ❌ NO import traceback │
│ │
│ ✅ frappe.throw() - Stop execution, show error │
│ ✅ frappe.log_error() - Log to Error Log doctype │
│ ✅ frappe.msgprint() - Show message, continue execution │
│ ✅ Conditional checks before operations │
│ │
└─────────────────────────────────────────────────────────────────────┘
Main Decision: How to Handle the Error?
┌─────────────────────────────────────────────────────────────────────────┐
│ WHAT TYPE OF ERROR ARE YOU HANDLING? │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ► Validation error (must stop save/submit)? │
│ └─► frappe.throw() with clear message │
│ │
│ ► Warning (inform user, allow continue)? │
│ └─► frappe.msgprint() with indicator │
│ │
│ ► Log error for debugging (no user impact)? │
│ └─► frappe.log_error() │
│ │
│ ► API error response (HTTP error)? │
│ └─► frappe.throw() with exc parameter OR set response │
│ │
│ ► Scheduler task error? │
│ └─► frappe.log_error() + continue processing other items │
│ │
│ ► Prevent operation but not with error dialog? │
│ └─► Return early + frappe.msgprint() │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Error Methods Reference
Quick Reference
| Method | Stops Execution? | User Sees? | Logged? | Use For |
|---|---|---|---|---|
frappe.throw() | ✅ YES | Dialog | Error Log | Validation errors |
frappe.msgprint() | ❌ NO | Dialog | No | Warnings |
frappe.log_error() | ❌ NO | No | Error Log | Debug/audit |
frappe.publish_realtime() | ❌ NO | Toast | No | Background updates |
frappe.throw() - Stop Execution
# Basic throw - stops execution, rolls back transaction
frappe.throw("Customer is required")
# With title
frappe.throw("Amount cannot be negative", title="Validation Error")
# With exception type (for API scripts)
frappe.throw("Not authorized", exc=frappe.PermissionError)
frappe.throw("Record not found", exc=frappe.DoesNotExistError)
# With formatted message
frappe.throw(
f"Credit limit exceeded. Limit: {credit_limit}, Requested: {amount}",
title="Credit Check Failed"
)
Exception Types for API Scripts:
| Exception | HTTP Code | Use For |
|---|---|---|
frappe.ValidationError | 417 | Validation failures |
frappe.PermissionError | 403 | Access denied |
frappe.DoesNotExistError | 404 | Record not found |
frappe.AuthenticationError | 401 | Not logged in |
frappe.OutgoingEmailError | 500 | Email send failed |
frappe.log_error() - Silent Logging
# Basic error log
frappe.log_error("Something went wrong", "My Script Error")
# With context data
frappe.log_error(
f"Failed to process invoice {doc.name}: {error_detail}",
"Invoice Processing Error"
)
# Log current exception (in controllers, not sandbox)
frappe.log_error(frappe.get_traceback(), "Unexpected Error")
frappe.msgprint() - Warning Without Stopping
# Simple warning
frappe.msgprint("Stock is running low", indicator="orange")
# With title
frappe.msgprint(
"This customer has pending payments",
title="Warning",
indicator="yellow"
)
# Alert style (top of page)
frappe.msgprint(
"Document will be processed in background",
alert=True
)
Error Handling Patterns by Script Type
Pattern 1: Document Event - Validation
# Type: Document Event
# Event: Before Save
# Collect all errors, show together
errors = []
if not doc.customer:
errors.append("Customer is required")
if doc.grand_total <= 0:
errors.append("Total must be greater than zero")
if not doc.items:
errors.append("At least one item is required")
else:
for idx, item in enumerate(doc.items, 1):
if not item.item_code:
errors.append(f"Row {idx}: Item Code is required")
if (item.qty or 0) <= 0:
errors.append(f"Row {idx}: Quantity must be positive")
# Throw all errors at once
if errors:
frappe.throw("<br>".join(errors), title="Validation Errors")
Pattern 2: Document Event - Conditional Warning
# Type: Document Event
# Event: Before Save
# Warning: doesn't stop save
credit_limit = frappe.db.get_value("Customer", doc.customer, "credit_limit") or 0
if credit_limit > 0 and doc.grand_total > credit_limit:
frappe.msgprint(
f"Order total ({doc.grand_total}) exceeds credit limit ({credit_limit})",
title="Credit Warning",
indicator="orange"
)
Pattern 3: Document Event - Safe Database Lookup
# Type: Document Event
# Event: Before Save
# Always validate before database lookup
if doc.customer:
customer_data = frappe.db.get_value(
"Customer",
doc.customer,
["credit_limit", "disabled", "territory"],
as_dict=True
)
# Check if customer exists
if not customer_data:
frappe.throw(f"Customer {doc.customer} not found")
# Check if disabled
if customer_data.disabled:
frappe.throw(f"Customer {doc.customer} is disabled")
# Use the data
doc.territory = customer_data.territory
Pattern 4: API Script - Error Responses
# Type: API
# Method: get_customer_info
customer = frappe.form_dict.get("customer")
# Validate required parameter
if not customer:
frappe.throw("Parameter 'customer' is required", exc=frappe.ValidationError)
# Check existence
if not frappe.db.exists("Customer", customer):
frappe.throw(f"Customer '{customer}' not found", exc=frappe.DoesNotExistError)
# Check permission
if not frappe.has_permission("Customer", "read", customer):
frappe.throw("You don't have permission to view this customer", exc=frappe.PermissionError)
# Success response
frappe.response["message"] = {
"customer": customer,
"credit_limit": frappe.db.get_value("Customer", customer, "credit_limit")
}
Pattern 5: Scheduler - Batch Processing with Error Isolation
# Type: Scheduler Event
# Cron: 0 9 * * * (daily at 9:00)
processed = 0
errors = []
invoices = frappe.get_all(
"Sales Invoice",
filters={"status": "Unpaid", "docstatus": 1},
fields=["name", "customer"],
limit=100 # ALWAYS limit in scheduler
)
for inv in invoices:
# Isolate errors per item - don't let one failure stop all
if not frappe.db.exists("Customer", inv.customer):
errors.append(f"{inv.name}: Customer not found")
continue
# Safe processing
result = process_invoice(inv.name)
if result.get("success"):
processed += 1
else:
errors.append(f"{inv.name}: {result.get('error', 'Unknown error')}")
# Log summary
if errors:
frappe.log_error(
f"Processed: {processed}, Errors: {len(errors)}\n\n" + "\n".join(errors),
"Invoice Processing Summary"
)
# REQUIRED: commit in scheduler
frappe.db.commit()
def process_invoice(invoice_name):
"""Helper function with error handling"""
# Validate invoice exists
if not frappe.db.exists("Sales Invoice", invoice_name):
return {"success": False, "error": "Invoice not found"}
# Process logic here
return {"success": True}
Pattern 6: Permission Query - Safe Fallback
# Type: Permission Query
# DocType: Sales Invoice
# Safe role check
user_roles = frappe.get_roles(user) or []
if "System Manager" in user_roles:
conditions = "" # Full access
elif "Sales Manager" in user_roles:
# Manager sees team's invoices
team = frappe.db.get_value("User", user, "department")
if team:
conditions = f"`tabSales Invoice`.department = {frappe.db.escape(team)}"
else:
conditions = f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"
elif "Sales User" in user_roles:
# User sees only own invoices
conditions = f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"
else:
# No access - return impossible condition
conditions = "1=0"
See:
references/patterns.mdfor more error handling patterns.
Transaction Behavior
Automatic Rollback on frappe.throw()
# Type: Document Event - Before Save
# All changes roll back if throw is called
doc.status = "Processing" # This change...
frappe.db.set_value("Counter", "main", "count", 100) # ...and this...
if some_condition_fails:
frappe.throw("Validation failed") # ...are ALL rolled back
Manual Commit in Scheduler
# Type: Scheduler Event
# Changes are NOT auto-committed in scheduler
for item in items:
frappe.db.set_value("Item", item.name, "last_sync", frappe.utils.now())
# REQUIRED: Explicit commit
frappe.db.commit()
Partial Commit Pattern (Scheduler)
# Type: Scheduler Event
# Process in batches with intermediate commits
BATCH_SIZE = 50
items = frappe.get_all("Item", filters={"sync_pending": 1}, limit=500)
for i in range(0, len(items), BATCH_SIZE):
batch = items[i:i + BATCH_SIZE]
for item in batch:
frappe.db.set_value("Item", item.name, "sync_pending", 0)
# Commit after each batch - partial progress saved
frappe.db.commit()
Critical Rules
✅ ALWAYS
- Validate inputs before database operations - Check existence before get_doc
- Use
frappe.db.escape()for user input in SQL - Prevent SQL injection - Add
limitto queries in Scheduler scripts - Prevent memory issues - Call
frappe.db.commit()in Scheduler scripts - Changes aren't auto-saved - Collect multiple errors before throwing - Better user experience
- Log errors in Scheduler scripts - No user to see the error
❌ NEVER
- Don't use try/except in Server Scripts - Blocked by sandbox
- Don't use
raisestatement - Usefrappe.throw()instead - Don't call
doc.save()in Before Save event - Framework handles it - Don't assume database values exist - Always check first
- Don't ignore empty results - Handle gracefully
Quick Reference: Error Message Quality
# ❌ BAD - Technical, not actionable
frappe.throw("KeyError: customer")
frappe.throw("NoneType has no attribute 'name'")
frappe.throw("Query failed")
# ✅ GOOD - Clear, actionable
frappe.throw("Please select a customer before saving")
frappe.throw(f"Customer '{doc.customer}' not found. Please verify the customer exists.")
frappe.throw("Could not calculate totals. Please ensure all items have valid quantities.")
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-serverscripts- Server Script syntaxerpnext-impl-serverscripts- Implementation workflowserpnext-errors-clientscripts- Client-side error handlingerpnext-database- Database operations
Score
Total Score
75/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon

