Skip to main content
ClaudeWave
Install in Claude Code
Copy
git clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-syntax-hooks && cp -r /tmp/frappe-syntax-hooks/skills/source/syntax/frappe-syntax-hooks ~/.claude/skills/frappe-syntax-hooks
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Frappe Configuration Hooks (hooks.py)

Configuration hooks in hooks.py enable custom apps to extend Frappe/ERPNext
behavior. This skill covers ALL non-document-event hooks. For `doc_events`
(validate, on_submit, on_update, etc.), see **frappe-syntax-hooks-events**.

## Quick Reference: Hook Categories

| Category | Key Hooks | Reference |
|----------|-----------|-----------|
| App metadata | `app_name`, `app_title`, `required_apps` | Below |
| Frontend assets | `app_include_js/css`, `web_include_js/css` | Below |
| Install/migrate | `before_install`, `after_install`, `after_migrate` | Below |
| Scheduler | `hourly`, `daily`, `cron`, `*_long` | [scheduler-events.md](references/scheduler-events.md) |
| Session/auth | `on_login`, `on_logout`, `auth_hooks` | [bootinfo.md](references/bootinfo.md) |
| Request middleware | `before_request`, `after_request` | [request-lifecycle.md](references/request-lifecycle.md) |
| Permissions | `permission_query_conditions`, `has_permission` | [permissions.md](references/permissions.md) |
| DocType overrides | `override_doctype_class`, `doctype_js` | [overrides.md](references/overrides.md) |
| Website/portal | `website_route_rules`, `portal_menu_items` | [request-lifecycle.md](references/request-lifecycle.md) |
| File handling | `before_write_file`, `write_file` | Below |
| Email | `override_email_send`, `default_mail_footer` | Below |
| PDF | `pdf_header_html`, `pdf_footer_html` | Below |
| Jinja | `jinja.methods`, `jinja.filters` | Below |
| Boot/client data | `extend_bootinfo`, `notification_config` | [bootinfo.md](references/bootinfo.md) |
| Data/fixtures | `fixtures`, `global_search_doctypes` | Below |
| Method overrides | `override_whitelisted_methods`, `standard_queries` | [overrides.md](references/overrides.md) |

---

## Decision Tree: Which Hook Do I Need?

```
What do you want to achieve?
|
+-- ADD JS/CSS to desk or portal?
|   +-- Desk --> app_include_js / app_include_css
|   +-- Portal --> web_include_js / web_include_css
|   +-- Specific form --> doctype_js
|   +-- List view --> doctype_list_js
|
+-- RUN periodic background tasks?
|   +-- < 5 min execution --> hourly / daily / weekly / monthly
|   +-- 5-25 min execution --> hourly_long / daily_long / etc.
|   +-- Exact time needed --> cron
|   See: frappe-syntax-hooks > scheduler-events.md
|
+-- SEND data to client at page load?
|   +-- extend_bootinfo
|
+-- MODIFY controller of existing DocType?
|   +-- v16+ --> extend_doctype_class (RECOMMENDED)
|   +-- v14/v15 --> override_doctype_class (last app wins)
|
+-- MODIFY API endpoint?
|   +-- override_whitelisted_methods
|
+-- CUSTOMIZE permissions?
|   +-- List filtering --> permission_query_conditions
|   +-- Document-level --> has_permission
|
+-- REACT to document save/submit/delete?
|   +-- See frappe-syntax-hooks-events skill
|
+-- EXPORT/IMPORT configuration?
|   +-- fixtures
|
+-- SETUP on install or migrate?
|   +-- after_install / after_migrate
|
+-- ADD custom Jinja functions?
|   +-- jinja.methods / jinja.filters
|
+-- CUSTOMIZE website routing?
|   +-- website_route_rules
|   See: request-lifecycle.md for full routing pipeline
|
+-- INTERCEPT every request/response?
|   +-- before_request / after_request
|   See: request-lifecycle.md for lifecycle flow
|
+-- CUSTOM page rendering?
|   +-- page_renderer hook
|   See: request-lifecycle.md for renderer architecture
```

---

## 1. App Metadata Hooks

ALWAYS include these in every hooks.py:

```python
app_name = "myapp"
app_title = "My App"
app_publisher = "My Company"
app_description = "Custom ERPNext extensions"
app_email = "info@mycompany.com"
app_license = "MIT"
required_apps = ["erpnext"]  # Declare dependencies
```

---

## 2. Frontend Asset Injection

```python
# Desk (backend UI) assets — loaded on EVERY desk page
app_include_js = "/assets/myapp/js/myapp.min.js"       # string or list
app_include_css = "/assets/myapp/css/myapp.min.css"

# Website/portal assets — loaded on EVERY web page
web_include_js = "/assets/myapp/js/web.min.js"
web_include_css = "/assets/myapp/css/web.min.css"

# Web form specific assets
webform_include_js = {"My Web Form": "public/js/my_webform.js"}
webform_include_css = {"My Web Form": "public/css/my_webform.css"}

# Form script extensions (extend OTHER apps' forms)
doctype_js = {"Sales Invoice": "public/js/sales_invoice.js"}

# List view script extensions
doctype_list_js = {"Sales Invoice": "public/js/sales_invoice_list.js"}

# Custom sounds
sounds = [{"name": "alert", "src": "/assets/myapp/sounds/alert.mp3", "volume": 0.5}]
```

NEVER put heavy libraries in `app_include_js` — they load on every page.

---

## 3. Installation & Migration Lifecycle

```python
before_install = "myapp.setup.before_install"
after_install = "myapp.setup.after_install"
after_sync = "myapp.setup.after_sync"            # After fixture sync
before_migrate = "myapp.setup.before_migrate"
after_migrate = "myapp.setup.after_migrate"
before_uninstall = "myapp.setup.before_uninstall"
after_uninstall = "myapp.setup.after_uninstall"
before_tests = "myapp.setup.seed_test_data"
```

All accept a single dotted-path string. The function receives no arguments.

---

## 4. Scheduler Events

See [scheduler-events.md](references/scheduler-events.md) for full reference.

```python
scheduler_events = {
    "all": ["myapp.tasks.every_minute"],            # ~60s interval
    "hourly": ["myapp.tasks.hourly_check"],         # default queue, 5 min timeout
    "daily": ["myapp.tasks.daily_report"],
    "weekly": ["myapp.tasks.weekly_cleanup"],
    "monthly": ["myapp.tasks.monthly_summary"],
    "daily_long": ["myapp.tasks.heavy_sync"],       # long queue, 25 min timeout
    "cron": {
        "0 9 * * 1-5": ["myapp.tasks.weekday_morning"]  # cron expression
    }
}
```

ALWAYS run `bench --site sitename migrate` after changing scheduler_events.
NEVER define task functions with arguments — they receive none.

---

## 5. Session & Authentication Hooks

```python
on_login = "myapp.auth.on_login"