OpenAEC-Foundation

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.

OpenAEC-Foundation 195 57 Updated 9mo ago

Resources

1
GitHub

Install

npx skillscat add openaec-foundation/erpnext-anthropic-claude-development-skill-package/erpnext-syntax-serverscripts

Install via the SkillsCat registry.

SKILL.md

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 /api/method/{name}
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_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