---
title: "Hooks"
space: "Frappe Framework"
url: "https://www.prilk.com/docs/frappe-framework/hooks"
updated: "2026-05-14"
---

## Hooks

Hooks let an app extend Frappe and other apps without forking their code. Every app declares its hooks in `hooks.py`. This page covers both the concept and the full reference.

### Why hooks
Frappe's philosophy: **extend, don't fork**. If you need to react when a Sales Invoice is submitted, don't edit ERPNext — write a hook in your own app. Updates to ERPNext don't conflict with your code, and your customisation survives every upgrade.

### How hooks resolve
Multiple apps can hook the same event. Frappe runs them all in install order. None blocks the others unless one raises an exception — that aborts the operation for everyone. Design your hooks idempotent (safe to re-run) and lightweight.

---

## Reference: All available hooks

### Installation lifecycle

```python
before_install = "my_app.install.before_install"
after_install = "my_app.install.after_install"
after_migrate = "my_app.install.after_migrate"
before_uninstall = "my_app.install.before_uninstall"
after_uninstall = "my_app.install.after_uninstall"
```

### Document events

```python
doc_events = {
    "Sales Invoice": {
        "validate": "my_app.custom.sales_invoice.validate",
        "before_save": "my_app.custom.sales_invoice.before_save",
        "on_update": "my_app.custom.sales_invoice.on_update",
        "before_submit": "my_app.custom.sales_invoice.before_submit",
        "on_submit": "my_app.custom.sales_invoice.on_submit",
        "before_cancel": "my_app.custom.sales_invoice.before_cancel",
        "on_cancel": "my_app.custom.sales_invoice.on_cancel",
        "on_trash": "my_app.custom.sales_invoice.on_trash",
    },
    "*": {
        "on_update": "my_app.audit.log_change",  # use sparingly — fires on every DocType
    }
}
```

### Scheduler

```python
scheduler_events = {
    "all": ["my_app.tasks.all"],              # Every minute
    "hourly": ["my_app.tasks.hourly"],
    "daily": ["my_app.tasks.daily"],
    "weekly": ["my_app.tasks.weekly"],
    "monthly": ["my_app.tasks.monthly"],
    "cron": {
        "0 9 * * 1-5": ["my_app.tasks.weekday_morning"]
    }
}
```

### Permissions

```python
permission_query_conditions = {
    "Sales Invoice": "my_app.permissions.sales_invoice_query",
}
has_permission = {
    "Sales Invoice": "my_app.permissions.sales_invoice_has_permission",
}
```

### DocType override

```python
override_doctype_class = {
    "Sales Invoice": "my_app.overrides.CustomSalesInvoice"
}
```

### UI inclusion

```python
app_include_js = "my_app.bundle.js"               # Desk only
app_include_css = "my_app.bundle.css"
web_include_js = "my_portal.bundle.js"            # Portal only
web_include_css = "my_portal.bundle.css"

doctype_js = {
    "Customer": "public/js/customer.js"
}
doctype_list_js = {
    "Sales Invoice": "public/js/sales_invoice_list.js"
}
```

### Routing & website

```python
website_route_rules = [
    {"from_route": "/orders/<name>", "to_route": "order-detail"},
]
update_website_context = ["my_app.context.update_context"]
```

### Fixtures

```python
fixtures = [
    {"dt": "Role", "filters": [["name", "in", ["My Custom Role"]]]},
    {"dt": "Custom Field", "filters": [["dt", "in", ["Customer", "Supplier"]]]},
]
```

### Whitelisted methods

```python
override_whitelisted_methods = {
    "frappe.client.get_count": "my_app.custom.get_count"
}
```

### Boot session

```python
boot_session = "my_app.boot.boot_session"        # Add data to frappe.boot
```

---

For canonical updates and any newer hooks, see [frappeframework.com/docs/v15/user/en/python-api/hooks](https://frappeframework.com/docs/v15/user/en/python-api/hooks).
