Skip to content
erpnext-impl-hooks logo

ERPNext Hooks - Implementation

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 ...

SKILL.md

Full skill instructions

ERPNext Hooks - Implementation

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)

Main Decision: What Are You Trying to Do?

┌─────────────────────────────────────────────────────────────────────────┐
│ 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                                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Decision Tree: doc_events vs Controller Methods

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.


Decision Tree: Which doc_event?

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

Decision Tree: Scheduler Event Type

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

Decision Tree: Override vs Extend (V16)

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!

Implementation Workflow: doc_events

Step 1: Add to hooks.py

# myapp/​hooks.py
doc_events = {
    "Sales Invoice": {
        "validate": "myapp.events.sales_invoice.validate",
        "on_submit": "myapp.events.sales_invoice.on_submit"
    }
}

Step 2: Create handler module

# 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)

Step 3: Deploy

bench --site sitename migrate

Implementation Workflow: scheduler_events

Step 1: Add to hooks.py

# 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"]
    }
}

Step 2: Create task module

# 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

Step 3: Deploy and verify

bench --site sitename migrate
bench --site sitename scheduler enable
bench --site sitename scheduler status

Implementation Workflow: extend_doctype_class (V16+)

Step 1: Add to hooks.py

# myapp/​hooks.py
extend_doctype_class = {
    "Sales Invoice": ["myapp.extensions.SalesInvoiceMixin"]
}

Step 2: Create mixin class

# 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")

Step 3: Deploy

bench --site sitename migrate

Implementation Workflow: Permission Hooks

Step 1: Add to hooks.py

# myapp/​hooks.py
permission_query_conditions = {
    "Sales Invoice": "myapp.permissions.si_query"
}
has_permission = {
    "Sales Invoice": "myapp.permissions.si_permission"
}

Step 2: Create permission handlers

# 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

Quick Reference: Handler Signatures

HookSignature
doc_eventsdef handler(doc, method=None):
rename eventsdef handler(doc, method, old, new, merge):
scheduler_eventsdef handler(): (no args)
extend_bootinfodef handler(bootinfo):
permission_querydef handler(user): → returns SQL string
has_permissiondef handler(doc, user=None, permission_type=None): → True/​False/​None
override methodsMust match original signature exactly

Critical Rules

1. Never commit in doc_events

# ❌ 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)

2. Use db_set_value after on_update

# ❌ 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")

3. Always call super() in overrides

# ❌ 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()

4. Always migrate after hooks changes

# Required after ANY hooks.py change
bench --site sitename migrate

5. permission_query only works with get_list

# ❌ 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={})

Version Differences

FeatureV14V15V16
doc_events✅✅✅
scheduler_events✅✅✅
override_doctype_class✅✅✅
extend_doctype_class❌❌✅
permission hooks✅✅✅
Scheduler tick4 min4 min60 sec

Reference Files

FileContents
decision-tree.mdComplete hook selection flowcharts
workflows.mdStep-by-step implementation patterns
examples.mdWorking code examples
anti-patterns.mdCommon mistakes and solutions

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

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...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

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...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

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.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View