to select ↑↓ to navigate
Frappe Framework

Frappe Framework

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

Hooks

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

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

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

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

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

DocType override

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

UI inclusion

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

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

Fixtures

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

Whitelisted methods

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

Boot session

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.

Last updated 3 days ago
Was this helpful?
Thanks!