You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use when writing Python code for ERPNext/Frappe Server Scripts including Document Events, API endpoints, Scheduler Events, and Permission Queries. Prevents the #1 AI mistake: using import statements in Server Scripts (sandbox blocks ALL imports). Covers frappe.* methods, event name mapping, and correct v14/v15/v16 syntax. Keywords: Server Script, frappe, ERPNext, sandbox, import, doc event, validate, on_submit, before_save, server script example, import not allowed, sandbox rules, which script type to use.
license
MIT
compatibility
Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16.
metadata
author
version
OpenAEC-Foundation
2.0
Frappe Server Scripts — Complete Reference
Server Scripts are Python scripts managed via Setup > Server Script in the
Frappe/ERPNext UI. They run inside a RestrictedPython sandbox.
CRITICAL: The Sandbox Rule
┌──────────────────────────────────────────────────────────────────┐
│ ALL import STATEMENTS ARE BLOCKED │
│ │
│ import json → ImportError: __import__ not found │
│ from datetime import * → ImportError: __import__ not found │
│ import frappe → ImportError (even frappe itself!) │
│ │
│ EVERYTHING you need is pre-loaded in the frappe namespace. │
│ NEVER write an import line. ALWAYS use frappe.utils.*, etc. │
└──────────────────────────────────────────────────────────────────┘
ALWAYS use the pre-loaded namespace instead of imports:
# v14: enabled by default# v15+: DISABLED by default — you MUST enable explicitly:
bench set-config -g server_script_enabled 1
# Or set server_script_enabled: true in site_config.json
NEVER expect Server Scripts to work on Frappe Cloud shared benches — they
require a private bench.
Script Types
Type
Trigger
Key Variable
Document Event
Document lifecycle (save, submit, cancel)
doc
API
HTTP request to /api/method/{name}
frappe.form_dict
Scheduler Event
Cron schedule
(none)
Permission Query
Document list filtering
user, conditions
Event Name Mapping (Document Events)
CRITICAL: The UI names differ from internal hook names:
Server Script UI
Internal Hook
Fires When
Before Insert
before_insert
Before new doc saved to DB
After Insert
after_insert
After first DB insert
Before Validate
before_validate
Before framework validation
Before Save
validate
Before save (new + update)
After Save
on_update
After successful save
Before Submit
before_submit
Before submit (docstatus 0→1)
After Submit
on_submit
After submit completes
Before Cancel
before_cancel
Before cancel (docstatus 1→2)
After Cancel
on_cancel
After cancel completes
Before Delete
on_trash
Before permanent delete
After Delete
after_delete
After permanent delete
NEVER confuse "Before Save" with before_save — the UI label "Before Save"
maps to the validate hook. The actual before_save hook runs AFTER validate.
Decision Tree: Server Script vs Document Controller
Need custom Python logic for a DocType?
│
├─► Can you install a custom Frappe app?
│ ├─► YES: Use a Document Controller when you need:
│ │ • import statements (any Python library)
│ │ • File system access
│ │ • Complex class inheritance
│ │ • autoname / before_naming hooks
│ │ • Unit-testable code
│ │
│ └─► NO: Use a Server Script when:
│ • You only have UI access (no bench CLI)
│ • Logic is simple validation / field calculation
│ • You need a quick API endpoint
│ • You need dynamic permission filtering
│
└─► Is logic > 50 lines or needs external libraries?
├─► YES → Document Controller in a custom app
└─► NO → Server Script is fine
open, file# No file I/Oeval, exec, compile# No dynamic code execution__import__# No imports (this is the root cause)globals, locals# No scope introspection
Syntax Per Script Type
Document Event
# Config: Reference DocType = Sales Invoice, Event = Before Saveifdoc.grand_total<0:
frappe.throw("Total MUST NOT be negative")
doc.requires_approval=1ifdoc.grand_total>10000else0