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.md
source 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

TypeUsageTrigger
Document EventReact to document lifecycleSave, Submit, Cancel, etc.
APICustom REST endpointHTTP request to
/api/method/{name}
Scheduler EventScheduled tasksCron schedule
Permission QueryDynamic list filteringDocument list view

Event Name Mapping (Document Event)

IMPORTANT: The UI event names differ from the internal hook names:

UI Name (Server Script)Internal HookWhen
Before Insert
before_insert
Before new doc to DB
After Insert
after_insert
After new doc saved
Before Validate
before_validate
Before validation
Before Save
validate
Before save (new or update)
After Save
on_update
After successful save
Before Submit
before_submit
Before submit
After Submit
on_submit
After submit
Before Cancel
before_cancel
Before cancel
After Cancel
on_cancel
After cancel
Before Delete
on_trash
Before delete
After 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

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