$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-syntax-serverscriptsUse 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 na
| 1 | # Frappe Server Scripts — Complete Reference |
| 2 | |
| 3 | Server Scripts are Python scripts managed via **Setup > Server Script** in the |
| 4 | Frappe/ERPNext UI. They run inside a **RestrictedPython sandbox**. |
| 5 | |
| 6 | ## CRITICAL: The Sandbox Rule |
| 7 | |
| 8 | ``` |
| 9 | ┌──────────────────────────────────────────────────────────────────┐ |
| 10 | │ ALL import STATEMENTS ARE BLOCKED │ |
| 11 | │ │ |
| 12 | │ import json → ImportError: __import__ not found │ |
| 13 | │ from datetime import * → ImportError: __import__ not found │ |
| 14 | │ import frappe → ImportError (even frappe itself!) │ |
| 15 | │ │ |
| 16 | │ EVERYTHING you need is pre-loaded in the frappe namespace. │ |
| 17 | │ NEVER write an import line. ALWAYS use frappe.utils.*, etc. │ |
| 18 | └──────────────────────────────────────────────────────────────────┘ |
| 19 | ``` |
| 20 | |
| 21 | **ALWAYS** use the pre-loaded namespace instead of imports: |
| 22 | |
| 23 | | Blocked import | Use instead | |
| 24 | |---|---| |
| 25 | | `import json` | `frappe.parse_json()` / `frappe.as_json()` | |
| 26 | | `from datetime import date` | `frappe.utils.today()` / `frappe.utils.now_datetime()` | |
| 27 | | `from frappe.utils import cint` | `frappe.utils.cint()` (already loaded) | |
| 28 | | `import requests` | `frappe.make_get_request()` / `frappe.make_post_request()` | |
| 29 | | `import re` | Not available — restructure logic without regex | |
| 30 | | `import os` / `import sys` | Not available — use a custom app instead | |
| 31 | |
| 32 | ## Enabling Server Scripts |
| 33 | |
| 34 | ```bash |
| 35 | # v14: enabled by default |
| 36 | # v15+: DISABLED by default — you MUST enable explicitly: |
| 37 | bench set-config -g server_script_enabled 1 |
| 38 | # Or set server_script_enabled: true in site_config.json |
| 39 | ``` |
| 40 | |
| 41 | **NEVER** expect Server Scripts to work on Frappe Cloud shared benches — they |
| 42 | require a private bench. |
| 43 | |
| 44 | ## Script Types |
| 45 | |
| 46 | | Type | Trigger | Key Variable | |
| 47 | |---|---|---| |
| 48 | | **Document Event** | Document lifecycle (save, submit, cancel) | `doc` | |
| 49 | | **API** | HTTP request to `/api/method/{name}` | `frappe.form_dict` | |
| 50 | | **Scheduler Event** | Cron schedule | (none) | |
| 51 | | **Permission Query** | Document list filtering | `user`, `conditions` | |
| 52 | |
| 53 | ## Event Name Mapping (Document Events) |
| 54 | |
| 55 | **CRITICAL**: The UI names differ from internal hook names: |
| 56 | |
| 57 | | Server Script UI | Internal Hook | Fires When | |
| 58 | |---|---|---| |
| 59 | | Before Insert | `before_insert` | Before new doc saved to DB | |
| 60 | | After Insert | `after_insert` | After first DB insert | |
| 61 | | Before Validate | `before_validate` | Before framework validation | |
| 62 | | **Before Save** | **`validate`** | Before save (new + update) | |
| 63 | | After Save | `on_update` | After successful save | |
| 64 | | Before Submit | `before_submit` | Before submit (docstatus 0→1) | |
| 65 | | After Submit | `on_submit` | After submit completes | |
| 66 | | Before Cancel | `before_cancel` | Before cancel (docstatus 1→2) | |
| 67 | | After Cancel | `on_cancel` | After cancel completes | |
| 68 | | Before Delete | `on_trash` | Before permanent delete | |
| 69 | | After Delete | `after_delete` | After permanent delete | |
| 70 | |
| 71 | **NEVER** confuse "Before Save" with `before_save` — the UI label "Before Save" |
| 72 | maps to the `validate` hook. The actual `before_save` hook runs AFTER `validate`. |
| 73 | |
| 74 | ## Decision Tree: Server Script vs Document Controller |
| 75 | |
| 76 | ``` |
| 77 | Need custom Python logic for a DocType? |
| 78 | │ |
| 79 | ├─► Can you install a custom Frappe app? |
| 80 | │ ├─► YES: Use a Document Controller when you need: |
| 81 | │ │ • import statements (any Python library) |
| 82 | │ │ • File system access |
| 83 | │ │ • Complex class inheritance |
| 84 | │ │ • autoname / before_naming hooks |
| 85 | │ │ • Unit-testable code |
| 86 | │ │ |
| 87 | │ └─► NO: Use a Server Script when: |
| 88 | │ • You only have UI access (no bench CLI) |
| 89 | │ • Logic is simple validation / field calculation |
| 90 | │ • You need a quick API endpoint |
| 91 | │ • You need dynamic permission filtering |
| 92 | │ |
| 93 | └─► Is logic > 50 lines or needs external libraries? |
| 94 | ├─► YES → Document Controller in a custom app |
| 95 | └─► NO → Server Script is fine |
| 96 | ``` |
| 97 | |
| 98 | ## Quick Reference: Available in Sandbox |
| 99 | |
| 100 | ### Pre-loaded Objects |
| 101 | |
| 102 | ```python |
| 103 | doc # Current document (Document Event only) |
| 104 | frappe # Core namespace — ALWAYS available |
| 105 | frappe.db # Database operations |
| 106 | frappe.utils # Date, number, string utilities |
| 107 | frappe.session # Cu |