Claude-skill-registry erpnext-syntax-serverscripts
Complete syntax reference for Frappe Server Scripts. Use this skill when Claude needs to write Python code for Server Scripts in ERPNext/Frappe, including Document Events, API endpoints, Scheduler Events, and Permission Queries. Covers sandbox limitations, available frappe.* methods, event name mapping, and correct syntax for v14/v15/v16.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/erpnext-syntax-serverscripts" ~/.claude/skills/majiayu000-claude-skill-registry-erpnext-syntax-serverscripts && rm -rf "$T"
manifest:
skills/data/erpnext-syntax-serverscripts/SKILL.mdsource content
ERPNext Server Scripts Syntax
Server Scripts are Python scripts that run within Frappe's secure sandbox environment. They are managed via Setup → Server Script in the ERPNext UI.
CRITICAL: Sandbox Limitations
┌─────────────────────────────────────────────────────────────────────┐ │ ⚠️ NO IMPORTS ALLOWED │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ The sandbox blocks ALL import statements: │ │ import json → ImportError: __import__ not found │ │ from datetime import date → ImportError │ │ │ │ SOLUTION: Use Frappe's pre-loaded namespace: │ │ frappe.utils.nowdate() not: from frappe.utils import nowdate │ │ frappe.parse_json(data) not: import json; json.loads(data) │ │ │ └─────────────────────────────────────────────────────────────────────┘
Server Script Types
| Type | Usage | Trigger |
|---|---|---|
| Document Event | React to document lifecycle | Save, Submit, Cancel, etc. |
| API | Custom REST endpoint | HTTP request to |
| Scheduler Event | Scheduled tasks | Cron schedule |
| Permission Query | Dynamic list filtering | Document list view |
Event Name Mapping (Document Event)
IMPORTANT: The UI event names differ from the internal hook names:
| UI Name (Server Script) | Internal Hook | When |
|---|---|---|
| Before Insert | | Before new doc to DB |
| After Insert | | After new doc saved |
| Before Validate | | Before validation |
| Before Save | | Before save (new or update) |
| After Save | | After successful save |
| Before Submit | | Before submit |
| After Submit | | After submit |
| Before Cancel | | Before cancel |
| After Cancel | | After cancel |
| Before Delete | | Before delete |
| After Delete | | After delete |
Quick Reference: Available API
Always available in sandbox
# Document object (in Document Event scripts) doc # Current document doc.name # Document name doc.doctype # DocType name doc.fieldname # Field value doc.get("fieldname") # Safe field access doc.items # Child table (list) # Frappe namespace frappe.db # Database operations frappe.get_doc() # Fetch document frappe.get_all() # Multiple documents frappe.throw() # Validation error frappe.msgprint() # User message frappe.log_error() # Error logging frappe.utils.* # Utility functions frappe.session.user # Current user frappe.form_dict # Request parameters (API) frappe.response # Response object (API)
Decision Tree: Which Script Type?
What do you want to achieve? │ ├─► React to document save/submit/cancel? │ └─► Document Event script │ ├─► Create REST API endpoint? │ └─► API script │ ├─► Run task on schedule? │ └─► Scheduler Event script │ └─► Filter document list view per user/role? └─► Permission Query script
Basic Syntax per Type
Document Event
# Configuration: # Reference DocType: Sales Invoice # DocType Event: Before Save (= validate) if doc.grand_total < 0: frappe.throw("Total cannot be negative") if doc.grand_total > 10000: doc.requires_approval = 1
API
# Configuration: # API Method: get_customer_info # Allow Guest: No # Endpoint: /api/method/get_customer_info customer = frappe.form_dict.get("customer") if not customer: frappe.throw("Customer parameter required") data = frappe.get_all( "Sales Order", filters={"customer": customer, "docstatus": 1}, fields=["name", "grand_total"], limit=10 ) frappe.response["message"] = data
Scheduler Event
# Configuration: # Event Frequency: Cron # Cron Format: 0 9 * * * (daily at 9:00) overdue = frappe.get_all( "Sales Invoice", filters={"status": "Unpaid", "due_date": ["<", frappe.utils.today()]}, fields=["name", "customer"] ) for inv in overdue: frappe.log_error(f"Overdue: {inv.name}", "Invoice Reminder") frappe.db.commit()
Permission Query
# Configuration: # Reference DocType: Sales Invoice # Output: conditions string for WHERE clause user_roles = frappe.get_roles(user) if "System Manager" in user_roles: conditions = "" # Full access elif "Sales User" in user_roles: conditions = f"`tabSales Invoice`.owner = {frappe.db.escape(user)}" else: conditions = "1=0" # No access
References
- references/events.md - Complete event mapping and execution order
- references/methods.md - All available frappe.* methods in sandbox
- references/examples.md - 10+ working examples per script type
- references/anti-patterns.md - Sandbox limitations and common mistakes
Version Information
- Frappe v14+: Server Scripts fully supported
- Activation required:
bench --site [site] set-config server_script_enabled true - Frappe v15: No significant syntax changes for Server Scripts