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
Decision 1: Server Script vs Controller vs hooks.py?
WHAT DO YOU NEED?
│
├── Document automation (validation, auto-fill, notifications)?
│ ├── Need external libraries or complex transactions?
│ │ ├── YES → Controller in custom app
│ │ └── NO → Server Script: Document Event
│ └── Run on MULTIPLE doctypes?
│ ├── YES → hooks.py doc_events
│ └── NO → Server Script: Document Event
│
├── Custom REST API endpoint?
│ ├── Need file uploads, streaming, or complex auth?
│ │ ├── YES → @frappe.whitelist() in custom app
│ │ └── NO → Server Script: API
│ └── Need database transaction control?
│ ├── YES → Controller whitelisted method
│ └── NO → Server Script: API
│
├── Scheduled/background task?
│ ├── Simple task (< 100 records, no external calls)?
│ │ └── Server Script: Scheduler Event
│ ├── Complex task or external integrations?
│ │ └── hooks.py scheduler_events + custom method
│ └── Need frappe.enqueue() for background job?
│ └── Controller method (enqueue not in sandbox)
│
└── Dynamic permission filtering?
├── Filter list view per user/role?
│ └── Server Script: Permission Query
└── Custom has_permission logic?
└── hooks.py has_permission
Decision 2: Which Document Event?
WHAT TRIGGERS YOUR CODE?
│
├── BEFORE document operations
│ ├── Before any validation runs?
│ │ └── Before Validate (before_validate)
│ │ Use for: Pre-processing, setting defaults
│ │
│ ├── Validate data / auto-calculate before save?
│ │ └── Before Save (validate) ← MOST COMMON
│ │ Use for: Validation, calculations, auto-fill
│ │ Changes to doc: SAVED automatically
│ │
│ ├── Check conditions before submit?
│ │ └── Before Submit (before_submit)
│ │ Use for: Submit-time checks, approval validation
│ │
│ └── Prevent or validate cancel?
│ └── Before Cancel (before_cancel)
│ Use for: Check linked docs, block cancel
│
├── AFTER document operations
│ ├── React to new document (first save only)?
│ │ └── After Insert (after_insert)
│ │ Use for: Welcome emails, create related docs
│ │ doc.name: GUARANTEED to exist
│ │
│ ├── React to any save (new or update)?
│ │ └── After Save (on_update)
│ │ Use for: Audit logs, notifications, sync
│ │ Changes to doc: NOT saved — use db_set
│ │
│ ├── React to submission?
│ │ └── After Submit (on_submit)
│ │ Use for: Ledger entries, stock updates
│ │
│ └── React to cancellation?
│ └── After Cancel (on_cancel)
│ Use for: Reverse entries, cleanup
│
└── DELETE operations
├── Prevent or validate delete?
│ └── Before Delete (on_trash)
└── Cleanup after delete?
└── After Delete (after_delete)
Decision 3: Validation Location
WHERE SHOULD VALIDATION HAPPEN?
│
├── UX feedback only (can be bypassed via API)?
│ └── Client Script validate event
│
├── MUST always run, even via API/import?
│ ├── Simple validation (single doctype, no imports)?
│ │ └── Server Script: Before Save
│ └── Complex validation or multiple doctypes?
│ └── Controller validate method
│
└── Data integrity (can NEVER be violated)?
└── BOTH client + server validation
(Server is authoritative)
Decision 4: API Authentication
WHO CAN ACCESS YOUR API?
│
├── Anyone (public, no login)?
│ └── Server Script API + Allow Guest: Yes
│ ALWAYS validate/sanitize all inputs
│
├── Any logged-in user?
│ └── Server Script API + Allow Guest: No
│ Add: Permission check in script body
│
├── Specific roles only?
│ └── Server Script API + Allow Guest: No
│ Add: frappe.get_roles() check
│
└── External systems (API key auth)?
└── @frappe.whitelist() in custom app
Use: frappe.get_request_header("Authorization")