xiaohongshu
xiaohongshu
erpnext-impl-hooks
Implementation workflows and decision trees for Frappe hooks.py configuration. Use when determining HOW to implement doc_events, scheduler_events, override hooks, permission hooks, extend_bootinfo, fixtures, and asset includes. Covers V16 extend_doctype_class. Triggers: how do I hook, which hook ...
Full skill instructions
This skill helps you determine HOW to implement hooks.py configurations. For exact syntax, see erpnext-syntax-hooks.
Version: v14/v15/v16 compatible (with V16-specific features noted)
┌─────────────────────────────────────────────────────────────────────────┐
│ WHAT DO YOU WANT TO ACHIEVE? │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ► React to document events on OTHER apps' DocTypes? │
│ └── doc_events in hooks.py │
│ │
│ ► Run code periodically (hourly, daily, custom schedule)? │
│ └── scheduler_events │
│ │
│ ► Modify behavior of existing DocType controller? │
│ ├── V16+: extend_doctype_class (RECOMMENDED - multiple apps work) │
│ └── V14/V15: override_doctype_class (last app wins) │
│ │
│ ► Modify existing API endpoint behavior? │
│ └── override_whitelisted_methods │
│ │
│ ► Add custom permission logic? │
│ ├── List filtering: permission_query_conditions │
│ └── Document-level: has_permission │
│ │
│ ► Send data to client on page load? │
│ └── extend_bootinfo │
│ │
│ ► Export/import configuration between sites? │
│ └── fixtures │
│ │
│ ► Add JS/CSS to desk or portal? │
│ ├── Desk: app_include_js/css │
│ ├── Portal: web_include_js/css │
│ └── Specific form: doctype_js │
│ │
└─────────────────────────────────────────────────────────────────────────┘
WHERE IS THE DOCTYPE?
│
├─► DocType is in YOUR custom app?
│ └─► Use controller methods (doctype/xxx/xxx.py)
│ - Direct control over lifecycle
│ - Cleaner code organization
│
├─► DocType is in ANOTHER app (ERPNext, Frappe)?
│ └─► Use doc_events in hooks.py
│ - Only way to hook external DocTypes
│ - Can register multiple handlers
│
└─► Need to hook ALL DocTypes (logging, audit)?
└─► Use doc_events with wildcard "*"
Rule: Controller methods for YOUR DocTypes, doc_events for OTHER apps' DocTypes.
WHAT DO YOU NEED TO DO?
│
├─► Validate data or calculate fields?
│ ├─► Before any save → validate
│ └─► Only on new documents → before_insert
│
├─► React after document is saved?
│ ├─► Only first save → after_insert
│ ├─► Every save → on_update
│ └─► ANY change (including db_set) → on_change
│
├─► Handle submittable documents?
│ ├─► Before submit → before_submit
│ ├─► After submit → on_submit (ledger entries here)
│ ├─► Before cancel → before_cancel
│ └─► After cancel → on_cancel (reverse entries here)
│
├─► Handle document deletion?
│ ├─► Before delete (can prevent) → on_trash
│ └─► After delete (cleanup) → after_delete
│
└─► Handle document rename?
├─► Before rename → before_rename
└─► After rename → after_rename
HOW LONG DOES YOUR TASK RUN?
│
├─► < 5 minutes
│ │
│ │ HOW OFTEN?
│ ├─► Every ~60 seconds → all
│ ├─► Every hour → hourly
│ ├─► Every day → daily
│ ├─► Every week → weekly
│ ├─► Every month → monthly
│ └─► Specific time → cron
│
└─► > 5 minutes (up to 25 minutes)
│
│ HOW OFTEN?
├─► Every hour → hourly_long
├─► Every day → daily_long
├─► Every week → weekly_long
└─► Every month → monthly_long
⚠️ Tasks > 25 minutes: Split into chunks or use background jobs
FRAPPE VERSION?
│
├─► V16+
│ │
│ │ WHAT DO YOU NEED?
│ ├─► Add methods/properties to DocType?
│ │ └─► extend_doctype_class (RECOMMENDED)
│ │ - Multiple apps can extend same DocType
│ │ - Safer, less breakage on updates
│ │
│ └─► Completely replace controller logic?
│ └─► override_doctype_class (use sparingly)
│
└─► V14/V15
└─► override_doctype_class (only option)
⚠️ Last installed app wins!
⚠️ Always call super() in methods!
# myapp/hooks.py
doc_events = {
"Sales Invoice": {
"validate": "myapp.events.sales_invoice.validate",
"on_submit": "myapp.events.sales_invoice.on_submit"
}
}
# myapp/events/sales_invoice.py
import frappe
def validate(doc, method=None):
"""
Args:
doc: The document object
method: Event name ("validate")
Changes to doc ARE saved (before save event)
"""
if doc.grand_total < 0:
frappe.throw("Total cannot be negative")
# Calculate custom field
doc.custom_margin = doc.grand_total - doc.total_cost
def on_submit(doc, method=None):
"""
After submit - document already saved
Use frappe.db.set_value for additional changes
"""
create_external_record(doc)
bench --site sitename migrate
# myapp/hooks.py
scheduler_events = {
"daily": ["myapp.tasks.daily_cleanup"],
"daily_long": ["myapp.tasks.heavy_processing"],
"cron": {
"0 9 * * 1-5": ["myapp.tasks.weekday_report"]
}
}
# myapp/tasks.py
import frappe
def daily_cleanup():
"""NO arguments - scheduler calls with no args"""
old_logs = frappe.get_all(
"Error Log",
filters={"creation": ["<", frappe.utils.add_days(None, -30)]},
pluck="name"
)
for name in old_logs:
frappe.delete_doc("Error Log", name)
def heavy_processing():
"""Long task - use _long variant in hooks"""
for batch in get_batches():
process_batch(batch)
frappe.db.commit() # Commit per batch for long tasks
bench --site sitename migrate
bench --site sitename scheduler enable
bench --site sitename scheduler status
# myapp/hooks.py
extend_doctype_class = {
"Sales Invoice": ["myapp.extensions.SalesInvoiceMixin"]
}
# myapp/extensions.py
import frappe
from frappe.model.document import Document
class SalesInvoiceMixin(Document):
"""Mixin that extends Sales Invoice"""
@property
def profit_margin(self):
"""Add computed property"""
if self.grand_total:
return ((self.grand_total - self.total_cost) / self.grand_total) * 100
return 0
def validate(self):
"""Extend validation - ALWAYS call super()"""
super().validate()
self.validate_margin()
def validate_margin(self):
"""Custom validation logic"""
if self.profit_margin < 10:
frappe.msgprint("Warning: Low margin invoice")
bench --site sitename migrate
# myapp/hooks.py
permission_query_conditions = {
"Sales Invoice": "myapp.permissions.si_query"
}
has_permission = {
"Sales Invoice": "myapp.permissions.si_permission"
}
# myapp/permissions.py
import frappe
def si_query(user):
"""
Returns SQL WHERE clause for list filtering.
ONLY works with get_list, NOT get_all!
"""
if not user:
user = frappe.session.user
if "Sales Manager" in frappe.get_roles(user):
return "" # No filter - see all
# Regular users see only their own
return f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"
def si_permission(doc, user=None, permission_type=None):
"""
Document-level permission check.
Return: True (allow), False (deny), None (use default)
NOTE: Can only DENY, not grant additional permissions!
"""
if permission_type == "write" and doc.status == "Closed":
return False # Deny write on closed invoices
return None # Use default permission system
| Hook | Signature |
|---|---|
| doc_events | def handler(doc, method=None): |
| rename events | def handler(doc, method, old, new, merge): |
| scheduler_events | def handler(): (no args) |
| extend_bootinfo | def handler(bootinfo): |
| permission_query | def handler(user): → returns SQL string |
| has_permission | def handler(doc, user=None, permission_type=None): → True/False/None |
| override methods | Must match original signature exactly |
# ❌ WRONG - breaks transaction
def on_update(doc, method=None):
frappe.db.commit()
# ✅ CORRECT - Frappe commits automatically
def on_update(doc, method=None):
update_related(doc)
# ❌ WRONG - change is lost
def on_update(doc, method=None):
doc.status = "Processed"
# ✅ CORRECT
def on_update(doc, method=None):
frappe.db.set_value(doc.doctype, doc.name, "status", "Processed")
# ❌ WRONG - breaks core functionality
class CustomInvoice(SalesInvoice):
def validate(self):
self.my_validation()
# ✅ CORRECT
class CustomInvoice(SalesInvoice):
def validate(self):
super().validate() # FIRST!
self.my_validation()
# Required after ANY hooks.py change
bench --site sitename migrate
# ❌ NOT filtered by permission_query_conditions
frappe.db.get_all("Sales Invoice", filters={})
# ✅ Filtered by permission_query_conditions
frappe.db.get_list("Sales Invoice", filters={})
| Feature | V14 | V15 | V16 |
|---|---|---|---|
| doc_events | ✅ | ✅ | ✅ |
| scheduler_events | ✅ | ✅ | ✅ |
| override_doctype_class | ✅ | ✅ | ✅ |
| extend_doctype_class | ❌ | ❌ | ✅ |
| permission hooks | ✅ | ✅ | ✅ |
| Scheduler tick | 4 min | 4 min | 60 sec |
| File | Contents |
|---|---|
| decision-tree.md | Complete hook selection flowcharts |
| workflows.md | Step-by-step implementation patterns |
| examples.md | Working code examples |
| anti-patterns.md | Common mistakes and solutions |
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience