ERPNext API Error Handling
Patterns for handling errors in API development. For syntax details, see erpnext-api-patterns .
Version: v14/v15/v16 compatible
API Error Handling Overview
┌─────────────────────────────────────────────────────────────────────┐ │ API ERROR HANDLING DECISION │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Where is the error occurring? │ │ │ │ Server-side (Python)? │ │ ├── Validation error → frappe.throw() with clear message │ │ ├── Permission error → frappe.throw() + PermissionError │ │ ├── Not found → frappe.throw() + DoesNotExistError │ │ └── Unexpected → Log + generic error to client │ │ │ │ Client-side (JavaScript)? │ │ ├── frappe.call → Use error callback or .catch() │ │ └── frappe.xcall → Use try/catch with async/await │ │ │ │ External integration? │ │ └── requests library → try/except with specific exceptions │ │ │ └─────────────────────────────────────────────────────────────────────┘
HTTP Status Codes Reference
Code Meaning When Frappe Uses
200 Success Normal response
400 Bad Request Validation error
403 Forbidden Permission denied
404 Not Found Document doesn't exist
417 Expectation Failed frappe.throw() called
500 Server Error Unhandled exception
Server-Side Patterns
Basic Whitelisted Method
@frappe.whitelist() def update_status(docname, status): # Validate input if not docname: frappe.throw(_("Document name is required"), frappe.ValidationError)
if status not in ["Draft", "Submitted", "Cancelled"]:
frappe.throw(_("Invalid status: {0}").format(status))
try:
doc = frappe.get_doc("My DocType", docname)
doc.status = status
doc.save()
return {"success": True, "name": doc.name}
except frappe.DoesNotExistError:
frappe.throw(_("Document {0} not found").format(docname))
except frappe.PermissionError:
frappe.throw(_("Permission denied"), frappe.PermissionError)
Bulk Operation with Partial Failure
@frappe.whitelist() def bulk_update(items): items = frappe.parse_json(items) results = {"success": [], "failed": []}
for item in items:
try:
doc = frappe.get_doc("Item", item["name"])
doc.update(item)
doc.save()
results["success"].append(item["name"])
except Exception as e:
results["failed"].append({
"name": item["name"],
"error": str(e)
})
frappe.db.commit()
return results
Client-Side Patterns
frappe.call Error Handling
frappe.call({ method: "myapp.api.update_status", args: { docname: "DOC-001", status: "Submitted" }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({message: __("Updated"), indicator: "green"}); } }, error: function(r) { // Called on HTTP error or frappe.throw frappe.msgprint({ title: __("Error"), message: r.message || __("Operation failed"), indicator: "red" }); } });
async/await Pattern
async function updateDocument(docname, status) { try { const result = await frappe.xcall("myapp.api.update_status", { docname: docname, status: status }); return result; } catch (error) { console.error("API Error:", error); frappe.throw(__("Failed to update document")); } }
External API Pattern
import requests
def call_external_api(endpoint, data): try: response = requests.post( endpoint, json=data, timeout=30, headers={"Authorization": f"Bearer {get_api_key()}"} ) response.raise_for_status() return response.json() except requests.Timeout: frappe.log_error("External API timeout", "API Integration") frappe.throw(("External service timeout. Please try again.")) except requests.HTTPError as e: frappe.log_error(f"HTTP {e.response.status_code}", "API Integration") frappe.throw(("External service error")) except requests.RequestException as e: frappe.log_error(str(e), "API Integration") frappe.throw(_("Connection failed"))
Critical Rules
✅ ALWAYS
-
Validate input before processing
-
Use frappe.throw() for user-facing errors
-
Log unexpected errors with frappe.log_error()
-
Return structured responses from APIs
-
Handle both success and error in callbacks
❌ NEVER
-
Expose internal error details to users
-
Catch exceptions without logging
-
Return raw exception messages
-
Assume API calls will succeed
-
Skip input validation
Quick Reference: Error Responses
User-facing error (shows alert)
frappe.throw(_("Clear error message"))
Permission error (403)
frappe.throw(_("Not allowed"), frappe.PermissionError)
Validation error (400)
frappe.throw(_("Invalid input"), frappe.ValidationError)
Log error (no user message)
frappe.log_error(frappe.get_traceback(), "Error Title")
Reference Files
File Contents
patterns.md Detailed error handling patterns
examples.md Complete working examples
anti-patterns.md Common mistakes to avoid
See Also
-
erpnext-api-patterns
-
API implementation patterns
-
erpnext-syntax-whitelisted
-
Whitelisted method syntax
-
erpnext-errors-serverscripts
-
Server Script error handling