Skip to content
erpnext-syntax-hooks logo

ERPNext Syntax: Hooks (hooks.py)

erpnext-syntax-hooks

Deterministic syntax for Frappe hooks.py configuration. Use when Claude needs to configure app events, scheduler tasks, document events, fixtures, boot session, jenv customization, or website routing. Covers v14/v15/v16 including extend_doctype_class. Triggers: hooks.py, doc_events, scheduler_eve...

SKILL.md

Full skill instructions

ERPNext Syntax: Hooks (hooks.py)

Hooks in hooks.py enable custom apps to extend Frappe/​ERPNext functionality.

Quick Reference

doc_events - Document Lifecycle

# In hooks.py
doc_events = {
    "*": {
        "after_insert": "myapp.events.log_all_inserts"
    },
    "Sales Invoice": {
        "validate": "myapp.events.si_validate",
        "on_submit": "myapp.events.si_on_submit"
    }
}
# In myapp/​events.py
import frappe

def si_validate(doc, method=None):
    """doc = document object, method = event name"""
    if doc.grand_total < 0:
        frappe.throw("Total cannot be negative")

scheduler_events - Periodic Tasks

# In hooks.py
scheduler_events = {
    "daily": ["myapp.tasks.daily_cleanup"],
    "hourly_long": ["myapp.tasks.heavy_sync"],
    "cron": {
        "0 9 * * 1-5": ["myapp.tasks.weekday_morning"]
    }
}
# In myapp/​tasks.py
def daily_cleanup():
    """No arguments - called automatically"""
    frappe.db.delete("Log", {"creation": ["<", one_month_ago()]})

extend_bootinfo - Client Data Injection

# In hooks.py
extend_bootinfo = "myapp.boot.extend_boot"
# In myapp/​boot.py
def extend_boot(bootinfo):
    """bootinfo = dict that goes to frappe.boot"""
    bootinfo.my_setting = frappe.get_single("My Settings").value
// Client-side
console.log(frappe.boot.my_setting);

Most Used doc_events

EventWhenUse Case
validateBefore every saveValidation, calculations
on_updateAfter every saveNotifications, sync
after_insertAfter new docCreation-only actions
on_submitAfter submitLedger entries
on_cancelAfter cancelReverse entries
on_trashBefore deleteCleanup

Complete list: See doc-events.md


Scheduler Event Types

EventFrequencyQueue/​Timeout
hourlyEvery hourdefault / 5 min
dailyEvery daydefault / 5 min
weeklyEvery weekdefault / 5 min
monthlyEvery monthdefault / 5 min
hourly_longEvery hourlong / 25 min
daily_longEvery daylong / 25 min
cronCustom timingdefault / 5 min

Cron syntax and examples: See scheduler-events.md


Critical Rules

1. bench migrate after scheduler changes

# REQUIRED - otherwise changes won't be picked up
bench --site sitename migrate

2. No commits in doc_events

# ❌ WRONG
def on_update(doc, method=None):
    frappe.db.commit()  # Breaks transaction

# ✅ CORRECT - Frappe commits automatically
def on_update(doc, method=None):
    update_related_docs(doc)

3. Changes after on_update via db_set

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

4. Heavy tasks to _long queue

# ❌ WRONG - timeout after 5 min
scheduler_events = {
    "daily": ["myapp.tasks.process_all_records"]  # May take 20 min
}

# ✅ CORRECT - 25 min timeout
scheduler_events = {
    "daily_long": ["myapp.tasks.process_all_records"]
}

5. Tasks receive no arguments

# ❌ WRONG
def my_task(some_arg):
    pass

# ✅ CORRECT
def my_task():
    # Fetch data inside the function
    pass

Cron Syntax Cheatsheet

* * * * *
│ │ │ │ │
│ │ │ │ └── Day of week (0-6, Sun=0)
│ │ │ └──── Month (1-12)
│ │ └────── Day of month (1-31)
│ └──────── Hour (0-23)
└────────── Minute (0-59)
PatternMeaning
*/​5 * * * *Every 5 minutes
0 9 * * *Daily at 09:00
0 9 * * 1-5Weekdays at 09:00
0 0 1 * *First day of month
0 17 * * 5Friday at 17:00

doc_events vs Controller Hooks

Aspectdoc_events (hooks.py)Controller Methods
Locationhooks.pydoctype/​xxx/​xxx.py
ScopeHook OTHER doctypesOnly OWN doctype
Multiple handlers✅ Yes (list)❌ No
PriorityAfter controllerFirst
Wildcard (*)✅ Yes❌ No

Use doc_events when:

  • Hooking other apps' DocTypes from your custom app
  • Reacting to ALL DocTypes (wildcard)
  • Registering multiple handlers

Use controller methods when:

  • Working on your own DocType
  • You want full lifecycle control

Reference Files

FileContents
doc-events.mdAll document events, signatures, execution order
scheduler-events.mdScheduler types, cron syntax, timeouts
bootinfo.mdextend_bootinfo, session hooks
overrides.mdOverride and extend patterns
permissions.mdPermission hooks
fixtures.mdFixtures configuration
examples.mdComplete hooks.py examples
anti-patterns.mdMistakes and corrections

Configuration Hooks

Override DocType Controller

# In hooks.py
override_doctype_class = {
    "Sales Invoice": "myapp.overrides.CustomSalesInvoice"
}
# In myapp/​overrides.py
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice

class CustomSalesInvoice(SalesInvoice):
    def validate(self):
        super().validate()  # CRITICAL: always call super()!
        self.custom_validation()

Warning: Last installed app wins when multiple apps override the same DocType.

Override Whitelisted Methods

# In hooks.py
override_whitelisted_methods = {
    "frappe.client.get_count": "myapp.overrides.custom_get_count"
}
# Method signature MUST be identical to original!
def custom_get_count(doctype, filters=None, debug=False, cache=False):
    # Custom implementation
    return frappe.db.count(doctype, filters)

Permission Hooks

# In hooks.py
permission_query_conditions = {
    "Sales Invoice": "myapp.permissions.si_query_conditions"
}
has_permission = {
    "Sales Invoice": "myapp.permissions.si_has_permission"
}
# In myapp/​permissions.py
def si_query_conditions(user):
    """Returns SQL WHERE fragment for list filtering"""
    if not user:
        user = frappe.session.user
    
    if "Sales Manager" in frappe.get_roles(user):
        return ""  # No restrictions
    
    return f"`tabSales Invoice`.owner = {frappe.db.escape(user)}"

def si_has_permission(doc, user=None, permission_type=None):
    """Document-level permission check"""
    if permission_type == "write" and doc.status == "Closed":
        return False
    return None  # Fallback to default

Note: permission_query_conditions only works with get_list, NOT with get_all!

Fixtures

# In hooks.py
fixtures = [
    {"dt": "Custom Field", "filters": [["module", "=", "My App"]]},
    {"dt": "Property Setter", "filters": [["module", "=", "My App"]]},
    {"dt": "Role", "filters": [["name", "like", "MyApp%"]]}
]
# Export fixtures to JSON
bench --site sitename export-fixtures

Asset Includes

# In hooks.py

# Desk (backend) assets
app_include_js = "/​assets/​myapp/​js/​myapp.min.js"
app_include_css = "/​assets/​myapp/​css/​myapp.min.css"

# Website/​Portal assets
web_include_js = "/​assets/​myapp/​js/​web.min.js"
web_include_css = "/​assets/​myapp/​css/​web.min.css"

# Form script extensions
doctype_js = {
    "Sales Invoice": "public/​js/​sales_invoice.js"
}

Install/​Migrate Hooks

# In hooks.py
after_install = "myapp.setup.after_install"
after_migrate = "myapp.setup.after_migrate"
# In myapp/​setup.py
def after_install():
    create_default_roles()
    
def after_migrate():
    clear_custom_cache()

Complete Decision Tree

What do you want to achieve?
│
├─► REACT to document events from OTHER apps?
│   └─► doc_events
│
├─► Run PERIODIC tasks?
│   └─► scheduler_events
│       ├─► < 5 min → hourly/​daily/​weekly/​monthly
│       ├─► > 5 min → hourly_long/​daily_long/​etc.
│       └─► Specific time → cron
│
├─► Send DATA to CLIENT at page load?
│   └─► extend_bootinfo
│
├─► Modify CONTROLLER of existing DocType?
│   ├─► Frappe v16+ → extend_doctype_class (recommended)
│   └─► Frappe v14/​v15 → override_doctype_class
│
├─► Modify API ENDPOINT?
│   └─► override_whitelisted_methods
│
├─► Customize PERMISSIONS?
│   ├─► List filtering → permission_query_conditions
│   └─► Document-level → has_permission
│
├─► EXPORT/​IMPORT configuration?
│   └─► fixtures
│
├─► ADD JS/​CSS to desk or portal?
│   ├─► Desk → app_include_js/​css
│   ├─► Portal → web_include_js/​css
│   └─► Form specific → doctype_js
│
└─► SETUP on install/​migrate?
    └─► after_install, after_migrate

Version Differences

Featurev14v15v16
doc_events✅✅✅
scheduler_events✅✅✅
extend_bootinfo✅✅✅
override_doctype_class✅✅✅
extend_doctype_class❌❌✅
permission_query_conditions✅✅✅
has_permission✅✅✅
fixtures✅✅✅

Anti-Patterns Summary

❌ Wrong✅ Correct
frappe.db.commit() in handlerFrappe commits automatically
doc.field = x in on_updatefrappe.db.set_value()
Heavy task in dailyUse daily_long
Change scheduler without migrateAlways bench migrate
Sensitive data in bootinfoOnly public config
Override without super()Always super().method() first
get_all with permission_queryUse get_list
Fixtures without filtersFilter by module/​app

Full anti-patterns: See anti-patterns.md

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