frappe-controller
Generate Frappe-style DocType controllers with lifecycle methods for microservices.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Generate Frappe-style DocType controllers with lifecycle methods for microservices.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use when configuring hooks.py — doc_events, scheduler_events, override_whitelisted_methods, override_doctype_class, jinja, boot_session, permission_query_conditions, has_permission, and fixtures. Prevents silent hook failures from wrong paths or missing migrate after scheduler changes. Covers full hooks.py reference beyond document lifecycle events. Keywords: hooks.py, doc_events, scheduler_events, override method, permission query conditions, boot session, fixtures, override_doctype_class, extend_doctype_class.
Use when building or consuming Frappe REST APIs — auto /api/resource CRUD, @frappe.whitelist endpoints, token vs session auth, file uploads, and response handling. Prevents unauthorized exposure from missing permission checks on whitelisted methods. Covers /api/resource, /api/method, token auth, OAuth, file upload, error mapping. Keywords: whitelist API, /api/resource, /api/method, Frappe REST, token auth, api_key api_secret, call Frappe from outside, expand, filters.
Use when reading or writing Frappe data safely — get_doc, get_all, get_list, frappe.db.get_value, parameterized frappe.db.sql, transactions, bulk ops, and performance. Prevents SQL injection and permission leaks from wrong API choice. Covers ORM reads/writes, raw SQL, transactions, bulk_update, N+1 avoidance. Keywords: frappe.db.sql, frappe.get_all, get_value, set_value, bulk update, N+1, db transaction, get_list, SQL injection, parameterized query.
Use when implementing Frappe's permission model — roles, perm levels, user permissions, share, permission_query_conditions, has_permission hooks, and in-code checks. Prevents unauthorized access from missing server-side checks or broken row-level filters. Covers role permissions, field-level perm_level, User Permissions, share, hooks, frappe.has_permission. Keywords: permissions, role, user permission, perm level, restrict rows, frappe.has_permission, field level security, permission query conditions, share, PermissionError.
Use when debugging Frappe errors, using bench console for live inspection, analyzing tracebacks, or reading Frappe log files. Prevents wasted debugging time from ignoring log context, misreading tracebacks, and not using bench console effectively. Covers bench console, frappe.logger, error log DocType, traceback analysis, common error patterns, log file locations, pdb/debugger integration, VS Code DAP, profiling, Frappe Recorder, mariadb diagnostics. Keywords: debug, bench console, traceback, error log, frappe.logger, pdb, debugging, log analysis, inspect, VS Code, DAP, profiling, recorder, mariadb, monitor, ERPNext error, how to debug, find the bug, what went wrong, stack trace, error message..
Use when receiving vague or unclear ERPNext/Frappe development requests that need interpretation. Transforms requirements like 'make invoice auto-calculate' or 'add approval workflow' into concrete technical specifications. Determines which Frappe mechanisms to use and maps to the full 61-skill catalog. Keywords: vague requirement, clarify scope, translate business need, technical spec, implementation plan, what does this mean, unclear requirement, translate to code, how to build this.
| name | frappe-controller |
| description | Generate Frappe-style DocType controllers with lifecycle methods for microservices. |
Create document controller classes with lifecycle methods following Frappe patterns.
from frappe_microservice.controller import DocumentController
import frappe
class SalesOrder(DocumentController):
def validate(self):
if not self.customer:
self.throw("Customer is required")
self.calculate_total()
def before_insert(self):
if not self.status:
self.status = 'Draft'
if not self.transaction_date:
self.transaction_date = frappe.utils.today()
def after_insert(self):
self.send_order_notification()
def calculate_total(self):
self.grand_total = sum(item.amount for item in self.items) if self.items else 0
from frappe_microservice.controller import setup_controllers
app = create_microservice("my-service")
setup_controllers(app, controllers_directory="./controllers")
Available: before_validate, validate, before_insert, after_insert, before_update, after_update, before_save, after_save, before_delete, on_trash, on_cancel, on_submit
self.throw(message) - Raise validation errorself.get(field, default=None) - Get field valueself.set(field, value) - Set field valueself.has_value_changed(fieldname) - Check if changedself.get_value_before_save(fieldname) - Get old valuevalidate() for business rulesbefore_insert()after_insert() or after_update()self.throw() for validation errorssales_order.py → Class: SalesOrder → DocType: Sales OrderRemember: This skill is model-invoked. Claude will use it autonomously when detecting controller development needs.
The following material is from Frappe / ERPNext controller skills (frappe-syntax-controllers, frappe-impl-controllers). Use it when mapping lifecycle behavior or aligning with upstream Frappe patterns. Hook names in this microservice controller may differ slightly from core Frappe (e.g. on_update vs after_update); treat the semantics the same unless your SDK docs say otherwise.
What do you need to do?
|
+-- Validate data or calculate fields?
| +-- validate (changes to self ARE saved)
|
+-- Action AFTER save (emails, sync, linked docs)?
| +-- on_update (changes to self are NOT saved — use db_set / set_value)
|
+-- Only for NEW documents?
| +-- after_insert (runs once on first save only)
|
+-- Custom document name?
| +-- autoname (set self.name)
|
+-- Before/after SUBMIT?
| +-- Validate before submit? -> before_submit
| +-- Create entries after submit? -> on_submit
|
+-- Before/after CANCEL?
| +-- Check linked docs? -> before_cancel
| +-- Reverse entries? -> on_cancel
|
+-- Cleanup before delete?
| +-- on_trash
|
+-- React to ANY value change (including db_set)?
| +-- on_change (MUST be idempotent)
| Item | Convention |
|---|---|
| DocType name | Title Case (e.g. Sales Order) |
| Class name | PascalCase (e.g. SalesOrder) |
| File path (typical app) | module/doctype/sales_order/sales_order.py |
| Base class | from frappe.model.document import Document |
| Method | Role |
|---|---|
autoname() | Custom naming — set self.name |
validate() | Main validation — runs on every save; field changes on self persist |
on_update() | After DB write — assignments to self do not persist without db_set |
on_submit() / on_cancel() | Submittable workflow — implement as a matched pair |
@frappe.whitelist() | Expose method to Desk client (frm.call(...)) |
| Aspect | validate | on_update |
|---|---|---|
| When | Before DB write | After DB write |
self.x = y persisted? | Yes | No — use db_set or frappe.db.set_value |
| Abort save with throw? | Yes | Too late — document already saved |
on_update — use validate() (or before_submit / similar as appropriate).INSERT (new document)
before_insert -> before_naming -> autoname -> before_validate -> validate
-> before_save -> [db_insert] -> after_insert -> on_update -> on_change
SAVE (existing document)
before_validate -> validate -> before_save -> [db_update]
-> on_update -> on_change
SUBMIT (docstatus 0 -> 1)
before_validate -> validate -> before_submit -> [db_update]
-> on_submit -> on_update -> on_change
CANCEL (docstatus 1 -> 2)
before_cancel -> [db_update] -> on_cancel -> on_change
UPDATE AFTER SUBMIT
before_update_after_submit -> [db_update]
-> on_update_after_submit -> on_change
DELETE
on_trash -> [db_delete] -> after_delete
DISCARD [v15+]
before_discard -> [db_set docstatus=2] -> on_discard
on_update: direct self.field = value is not persisted — use self.db_set(...) or frappe.db.set_value(...).frappe.db.commit() inside controllers — Frappe commits at end of request; manual commit risks partial updates.super().validate() (and equivalents) when overriding hooks so base/ERPNext logic still runs unless you intentionally replace it.self.flags (or equivalent) for data passed between hooks in one transaction — avoid global/external mutable state for this.on_update — validate in validate() / before_submit as applicable.on_submit and on_cancel together — ALWAYS reverse on_submit side effects in on_cancel.NEED full Python (imports, classes, libs)? -> Controller
NEED ERPNext/custom app extension / background jobs? -> Controller
Quick validation without a custom app? -> Server Script (where enabled)
| Do NOT | Do instead |
|---|---|
Expect self.x = y in on_update to save | db_set / frappe.db.set_value |
self.save() recursively from on_update | Risks loops; use db_set or enqueue work |
frappe.db.commit() in controllers | Let the framework manage the transaction |
Heavy work blocking in validate | Consider frappe.enqueue() from on_update |
Skip super() in overrides | Call parent hooks first unless fully replacing behavior |
frappe.get_doc() in hot loops | Prefer frappe.get_cached_doc() when applicable |