Back to list
OpenAEC-Foundation

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

MethodStops Execution?User Sees?Logged?Use For
frappe.throw()✅ YESDialogError LogValidation errors
frappe.msgprint()❌ NODialogNoWarnings
frappe.log_error()❌ NONoError LogDebug/audit
frappe.publish_realtime()❌ NOToastNoBackground 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:

ExceptionHTTP CodeUse For
frappe.ValidationError417Validation failures
frappe.PermissionError403Access denied
frappe.DoesNotExistError404Record not found
frappe.AuthenticationError401Not logged in
frappe.OutgoingEmailError500Email 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.md for 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

  1. Validate inputs before database operations - Check existence before get_doc
  2. Use frappe.db.escape() for user input in SQL - Prevent SQL injection
  3. Add limit to queries in Scheduler scripts - Prevent memory issues
  4. Call frappe.db.commit() in Scheduler scripts - Changes aren't auto-saved
  5. Collect multiple errors before throwing - Better user experience
  6. Log errors in Scheduler scripts - No user to see the error

❌ NEVER

  1. Don't use try/except in Server Scripts - Blocked by sandbox
  2. Don't use raise statement - Use frappe.throw() instead
  3. Don't call doc.save() in Before Save event - Framework handles it
  4. Don't assume database values exist - Always check first
  5. 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

FileContents
references/patterns.mdComplete error handling patterns
references/examples.mdFull working examples
references/anti-patterns.mdCommon mistakes to avoid

See Also

  • erpnext-syntax-serverscripts - Server Script syntax
  • erpnext-impl-serverscripts - Implementation workflows
  • erpnext-errors-clientscripts - Client-side error handling
  • erpnext-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