# Frappe Hooks

> Working with hooks.py event patterns and configurations in Frappe.

- Skill: `av-dev2/frappe-hooks` (Agent Skill)
- Install (CLI): `npx skillmds@latest add av-dev2/frappe-hooks`
- Raw SKILL.md: https://api.skillmd.com/api/skills/av-dev2/frappe-hooks/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: av-dev2 (https://skillmd.com/u/av-dev2)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/av-dev2/frappe-hooks

---


# Hooks

`hooks.py` is the app's central configuration file. It tells Frappe how to integrate the app.

File: `apps/<app>/<app>/hooks.py`

## App metadata (auto-generated by bench new-app)

```python
app_name = "myapp"
app_title = "My App"
app_publisher = "Publisher"
app_description = "Description"
app_version = "0.0.1"
required_apps = ["frappe"]         # apps that must be installed first
```

## App install/uninstall hooks

```python
after_install = "myapp.setup.after_install"       # runs after app is installed on a site
before_uninstall = "myapp.setup.before_uninstall" # runs before app is removed
after_uninstall = "myapp.setup.after_uninstall"   # runs after app is removed
```

Use `after_install` to create initial data (roles, settings, default records).

## Document event hooks

Run Python functions when any DocType triggers lifecycle events:

```python
doc_events = {
    "Expense": {
        "validate": "myapp.overrides.expense.validate",
        "on_submit": "myapp.overrides.expense.on_submit",
    },
    "*": {  # all DocTypes
        "after_insert": "myapp.audit.log_creation",
    }
}
```

The function receives the document and event name:
```python
def validate(doc, method):
    if doc.amount > 10000:
        frappe.throw("Amount exceeds limit")
```

## Scheduled jobs

```python
scheduler_events = {
    "daily": ["myapp.tasks.daily_cleanup"],
    "hourly": ["myapp.tasks.sync_data"],
    "cron": {
        "*/5 * * * *": ["myapp.tasks.check_status"]
    }
}
```

See [background-jobs.md](./background-jobs.md) for details.

## Desk includes (inject CSS/JS into desk)

```python
app_include_css = "/assets/myapp/css/myapp.css"
app_include_js = "/assets/myapp/js/myapp.js"
```

## Override standard classes

```python
override_doctype_class = {
    "ToDo": "myapp.overrides.CustomToDo"
}
```

The override class MUST inherit from the original:
```python
# myapp/overrides.py
from frappe.desk.doctype.todo.todo import ToDo

class CustomToDo(ToDo):
    def validate(self):
        super().validate()
        # custom logic
```

Use sparingly — prefer `doc_events` hooks when possible.

## Whitelisted methods exposed via hooks

```python
# Override standard whitelisted methods, use sparingly
override_whitelisted_methods = {
    "frappe.client.get_count": "myapp.api.custom_get_count"
}
```

## Fixtures (export data with app)

```python
fixtures = [
    "Custom Field",
    {"dt": "Role", "filters": [["name", "in", ["Expense Manager"]]]},
]
```

Export: `bench --site <site> export-fixtures --app <app-name>`

## Website routes

```python
website_route_rules = [
    {"from_route": "/expenses", "to_route": "expenses/index"},
    {"from_route": "/expenses/<path:name>", "to_route": "expenses/view"},
]
```

## Common patterns

- Hook functions are dotted paths to importable Python functions
- Keep hook functions in dedicated files (e.g. `overrides/`, `tasks.py`)

## Other useful hooks

```python
# Add custom Jinja methods/filters available in templates
jinja = {
    "methods": ["myapp.utils.jinja.my_method"],
    "filters": ["myapp.utils.jinja.my_filter"],
}

# Add data to the boot response (loaded on Desk startup)
boot_session = "myapp.boot.boot_session"

# Permission query conditions for list filtering
permission_query_conditions = {
    "Expense": "myapp.permissions.expense_query_conditions",
}

# has_permission override
has_permission = {
    "Expense": "myapp.permissions.expense_has_permission",
}
```

