frappe-document-hooks
Generate document lifecycle hooks for DocTypes without modifying Frappe core.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Generate document lifecycle hooks for DocTypes without modifying Frappe core.
التثبيت باستخدام 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-document-hooks |
| description | Generate document lifecycle hooks for DocTypes without modifying Frappe core. |
Create document lifecycle hooks using frappe-microservice-lib hook system.
# Global hook - runs for ALL doctypes
@app.tenant_db.on('*', 'before_insert')
def ensure_tenant_id(doc):
from flask import g
if not doc.tenant_id and hasattr(g, 'tenant_id'):
doc.tenant_id = g.tenant_id
# DocType-specific hook
@app.tenant_db.on('Sales Order', 'before_insert')
def set_order_defaults(doc):
if not doc.status:
doc.status = 'Draft'
if not doc.transaction_date:
doc.transaction_date = frappe.utils.today()
@app.tenant_db.on('Sales Order', 'validate')
def validate_order(doc):
if not doc.customer:
frappe.throw("Customer is required")
if doc.grand_total and doc.grand_total < 0:
frappe.throw("Order total cannot be negative")
before_validate, validate, before_insert, after_insertbefore_update, after_update, before_save, after_savebefore_delete, after_delete# All hooks run in registration order
@app.tenant_db.on('Sales Order', 'before_insert')
def set_defaults(doc):
if not doc.status:
doc.status = 'Draft'
@app.tenant_db.on('Sales Order', 'before_insert')
def calculate_totals(doc):
# Runs after set_defaults
doc.calculate_totals()
@app.tenant_db.on('Sales Order', 'validate')
def validate_order(doc):
try:
if not doc.customer:
frappe.throw("Customer is required")
except frappe.ValidationError:
raise
except Exception as e:
frappe.log_error(f"Validation error: {e}")
'*' for hooks applying to all doctypesvalidate hook for business rulesfrappe.throw() for validation errorsRemember: This skill is model-invoked. Claude will use it autonomously when detecting hook development needs.
Canonical Frappe document lifecycle reference (hooks.py doc_events and DocType controllers). Use for parity when naming or ordering logic; framework event names (on_trash, on_update, etc.) may differ slightly from decorator names in code above.
Insert (new doc): before_insert → before_naming → autoname → before_validate → validate → before_save → (db_insert) → after_insert → on_update → on_change
Save (existing): before_validate → validate → before_save → (db_update) → on_update → on_change
Submit: before_validate → validate → before_save → before_submit → (db_update) → on_submit → on_update → on_change
Cancel: before_cancel → (db_update) → on_cancel → on_change
Delete: on_trash → after_delete
Other: Rename — before_rename → after_rename. Amend — insert chain runs on new amended doc. Update after submit — before_update_after_submit → (db_update) → on_update_after_submit → on_change
| Need | Event |
|---|---|
| Block invalid saves | validate (use frappe.throw()) |
| Defaults before validation | before_validate |
| Logic only on first create | after_insert (not on later saves) |
| Logic on every save (insert + update) | on_update |
| Side effects after submit (e.g. linked docs) | on_submit |
| Reverse/clean up on cancel | on_cancel |
| Name / naming rules | autoname or before_naming (controller) |
| Block deletion | on_trash (raise to abort) |
| Submitted-doc field updates | before_update_after_submit / on_update_after_submit |
| Only when field values changed vs DB | on_change |
Where to register: Own DocType → controller methods (preferred). Other app’s DocType → doc_events in hooks.py. All DocTypes → doc_events with "*" key.
App-level “which hook file” (non-doc): Periodic jobs → scheduler_events. Client boot data → extend_bootinfo. List/desk assets → app_include_js / doctype_js. Permissions → permission_query_conditions / has_permission. Replace whole controller (v14–15) → override_doctype_class; stack extensions (v16+) → extend_doctype_class.
doc_events handlers in app installation order; "*" wildcard handlers run after DocType-specific handlers.doc_events: Order follows installed apps (adjustable via Setup → Installed Applications → Update Hooks Resolution Order). override_doctype_class: only one winner (last installed app). extend_doctype_class (v16+): extensions stack (MRO / hook priority).before_validate through on_change, work is in one DB transaction; any exception rolls back. Do not assume other requests see writes until the request finishes. after_delete still runs in the request transaction context.method=None as second argument in doc_events handlers: def handler(doc, method=None):. For rename: def handler(doc, method, old, new, merge):.bench --site <site> migrate after changing hooks.py.hooks.py — never lambdas; never import frappe at module top level in hooks.py (runs before init).frappe.db.commit() inside doc_events / document hook handlers — Frappe owns the transaction.doc.save() inside validate or before_save — risks infinite recursion.validate — prefer on_submit / post-commit patterns where appropriate.doc.name outside autoname / before_naming.on_change for critical invariants — it only runs when values differ from DB.on_update on existing docs, direct doc attribute changes can be lost — use frappe.db.set_value() when the framework pattern requires it.super().<event>() so core logic is preserved.doc.flags to pass state between events in the same request; use doc.flags.ignore_permissions = True only when intentionally bypassing permissions.on_login → session created → on_session_creation → extend_bootinfo.