Skip to content
AI & Agents
Skill

/frappe-impl-hooks

Use when implementing hooks.py configurations in a Frappe custom app. Covers step-by-step workflows for doc_events, scheduler_events, override/extend_doctype_class, permission hooks, extend_bootinfo, fixtures, asset injection, website hooks, and doctype_js. Prevents broken

From plugin
frappe-claude-skill-package
17861 skills
Install
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-impl-hooks --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frappe-impl-hooks

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when implementing hooks.py configurations in a Frappe custom app. Covers step-by-step workflows for doc_events, scheduler_events, override/extend_doctype_class, permission hooks, extend_bootinfo, fixtures, asset injection, website hooks, and doctype_js. Prevents broken

SKILL.md

frappe-impl-hooks.SKILL.md
name: frappe-impl-hooks
description: >
  Use when implementing hooks.py configurations in a Frappe custom app.
  Covers step-by-step workflows for doc_events, scheduler_events,
  override/extend_doctype_class, permission hooks, extend_bootinfo,
  fixtures, asset injection, website hooks, and doctype_js.
  Prevents broken transactions, missed migrations, and multi-app conflicts.
  Keywords: hooks.py, doc_events, scheduler_events, override doctype,, how to add hook, when to use doc_events, scheduler setup, override existing behavior.
  extend doctype class, permission hook, scheduler job, fixtures,
  doctype_js, extend_bootinfo, website hooks.
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
  author: OpenAEC-Foundation
  version: "2.0"

Frappe Hooks Implementation Workflow

Step-by-step workflows for implementing hooks.py configurations. For API syntax reference, see `frappe-syntax-hooks`.

**Version**: v14/v15/v16 (V16-specific features noted)

---

Master Decision: What Are You Implementing?

WHAT DO YOU WANT TO ACHIEVE?
│
├─► React to document lifecycle events?
│   ├─► On OTHER app's DocTypes → doc_events in hooks.py
│   ├─► On YOUR OWN DocTypes → controller methods (preferred)
│   └─► On ALL DocTypes → doc_events with "*" wildcard
│
├─► Run code on a schedule?
│   └─► scheduler_events (daily, hourly, cron, etc.)
│
├─► Modify an existing DocType's behavior?
│   ├─► V16+: extend_doctype_class (RECOMMENDED)
│   └─► V14/V15: override_doctype_class (last app wins!)
│
├─► Override an existing API endpoint?
│   └─► override_whitelisted_methods
│
├─► Add custom permission logic?
│   ├─► List filtering → permission_query_conditions
│   └─► Document-level → has_permission
│
├─► Send config data to client on page load?
│   └─► extend_bootinfo
│
├─► Export/import configuration?
│   └─► fixtures
│
├─► Add JS/CSS to desk or portal?
│   ├─► Desk-wide → app_include_js / app_include_css
│   ├─► Portal-wide → web_include_js / web_include_css
│   └─► Specific form → doctype_js
│
├─► Customize website/portal behavior?
│   └─► website_context, portal_menu_items, website_route_rules
│
└─► Hook into session/auth lifecycle?
    └─► on_login, on_session_creation, on_logout

---

Workflow 1: Implementing doc_events

When to Use

Use doc_events when you need to react to document lifecycle events on DocTypes owned by OTHER apps (ERPNext, Frappe core). For YOUR OWN DocTypes, ALWAYS prefer controller methods.

Step-by-Step

**Step 1: Choose the right event** (see `references/decision-tree.md`)

BEFORE save: validate (every save), before_insert (new only)
AFTER save:  after_insert (new only), on_update (every save), on_change (any change)
SUBMIT flow: before_submit → on_submit → on_change
CANCEL flow: before_cancel → on_cancel → on_change
DELETE:      on_trash (before), after_delete (after)
RENAME:      before_rename, after_rename

**Step 2: 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 3: Create handler module**

# myapp/events/sales_invoice.py
import frappe

def validate(doc, method=None):
    """Changes to doc ARE saved (before-save event)."""
    if doc.grand_total < 0:
        frappe.throw("Total cannot be negative")

def on_submit(doc, method=None):
    """Document already saved. Use db_set_value for changes."""
    frappe.db.set_value("Sales Invoice", doc.name,
                        "custom_external_id", create_external(doc))

**Step 4: Deploy**

bench --site sitename migrate

**Step 5: Test**

bench --site sitename execute myapp.events.sales_invoice.validate --kwargs '{"doc_name": "INV-001"}'
# Or in bench console:
# doc = frappe.get_doc("Sales Invoice", "INV-001"); doc.save()

Critical Rules for doc_events

  • **NEVER** call `frappe.db.commit()` inside a doc_event handler — Frappe manages the transaction
  • **NEVER** modify `doc` fields in `on_update` — changes are lost; use `frappe.db.set_value()` instead
  • **ALWAYS** accept `method=None` as second parameter in handler signature
  • **ALWAYS** use rename signature: `def handler(doc, method, old, new, merge)`
  • **ALWAYS** run `bench --site sitename migrate` after changing hooks.py

---

Workflow 2: Implementing scheduler_events

Step-by-Step

**Step 1: Choose frequency**

| Frequency | Short (< 5 min) | Long (5-25 min) | |-----------|-----------------|------------------| | Every tick | `all` | — | | Hourly | `hourly` | `hourly_long` | | Daily | `daily` | `daily_long` | | Weekly | `weekly` | `weekly_long` | | Monthly | `monthly` | `monthly_long` | | Custom | `cron` | `cron` (use long queue manually) |

**Step 2: Add to hooks.py**

scheduler_events = {
    "daily": ["myapp.tasks.daily_cleanup"],
    "daily_long": ["myapp.tasks.heavy_sync"],
    "cron": {
        "0 9 * * 1-5": ["myapp.tasks.weekday_report"]
    }
}

**Step 3: Implement task (NO arguments)**

# myapp/tasks.py
import frappe

def daily_cleanup():
    """Scheduler calls with NO arguments."""
    frappe.db.delete("Error Log", {
        "creation": ["<", frappe.utils.add_days(None, -30)]
    })
    frappe.db.commit()

def heavy_sync():
    """Long task — commit periodically."""
    records = get_records_to_sync()
    for i, record in enumerate(records):
        process(record)
        if i % 100 == 0:
            frappe.db.commit()
    frappe.db.commit()

**Step 4: Deploy and verify**

bench --site sitename migrate
bench --site sitename scheduler enable
bench --site sitename scheduler status
# Test manually:
bench --site sitename execute myapp.tasks.daily_cleanup

Critical Rules for Scheduler

  • **NEVER** add parameters to scheduler task functions — the scheduler passes none
  • **ALWAYS** use `_long` variants for tasks exceeding 5 minutes (defau
Read more
Ships withfrappe-claude-skill-package

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Get the whole plugin
Stats
178
Stars
53
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
8mo ago
Created
14d ago
Added

Repo: Impertio-Studio/Frappe_Claude_Skill_Package