Skip to content

ERPNext glossary

hooks.py

Also called Frappe hooks, app hooks, hooks file, doc_events

Definition

hooks.py is the configuration file inside every Frappe app's Python package where the app declares how it plugs into the framework, such as document event handlers, scheduled jobs, fixtures, form script includes and class overrides. Frappe merges the hooks of all apps installed on a site and applies them together.

Adding a credit check to Sales Order shouldn’t mean editing ERPNext’s own controller, because that edit blocks your next update. What you do instead is tell Frappe to call a function of yours whenever a Sales Order is validated. That instruction goes in hooks.py, which bench new-app creates at apps/<app>/<app>/hooks.py with most of the options stubbed out as comments.

How Frappe reads the file

A hook is plain Python: a module-level variable with a known name, holding a string, list or dict. Frappe doesn’t run your hooks in isolation. It loads the file from every app installed on the site and merges values that share a name into lists, which is why ERPNext’s doc_events and yours can both fire on the same save.

Some hooks use every collected value, like included JS files or document events. Others can only have one winner, like override_whitelisted_methods. For those, the app installed last on the site wins. The order can be changed from the Installed Applications page if you really need to.

Outside developer mode, the merged hooks are cached. Editing the file on a production bench does nothing visible until the cache is cleared and the processes restart. bench migrate clears the cache, and the docs say scheduler changes specifically need migrate before they take effect.

A realistic example

# acme_custom/hooks.py
app_name = "acme_custom"
app_title = "Acme Custom"

required_apps = ["erpnext"]

doc_events = {
    "Sales Order": {
        "validate": "acme_custom.sales_order.check_credit_hold",
        "on_submit": "acme_custom.sales_order.notify_warehouse",
    },
}

scheduler_events = {
    "daily": ["acme_custom.tasks.close_stale_quotations"],
    "cron": {
        "0 7 * * 1": ["acme_custom.tasks.send_weekly_ar_summary"],
    },
}

doctype_js = {"Sales Order": "public/js/sales_order.js"}

fixtures = [
    {"dt": "Custom Field", "filters": [["module", "=", "Acme Custom"]]},
]
# acme_custom/sales_order.py
import frappe


def check_credit_hold(doc, method=None):
    if frappe.db.get_value("Customer", doc.customer, "custom_credit_hold"):
        frappe.throw(f"{doc.customer} is on credit hold. Ask accounts to release it.")


def notify_warehouse(doc, method=None):
    ...

Every doc_events handler receives the document and the event name. The event keys match controller method names, so validate, on_update, on_submit and on_cancel all work, and "*" in place of a DocType name applies a handler to every DocType. This is the pattern erpfly generates when a requirement touches a standard ERPNext document: the logic sits in your app, wired through hooks.py, and ERPNext’s files stay untouched.

Hooks you’ll reach for first

  • doc_events for reacting to saves, submits and cancels on DocTypes you don’t own.
  • scheduler_events with hourly, daily, weekly, monthly, their _long variants for slow jobs, and cron for exact times.
  • fixtures to ship Custom Fields and other records with the app.
  • doctype_js to extend a standard form’s client script.
  • override_doctype_class to swap a DocType’s controller class. On v16, the docs recommend extend_doctype_class when you only need to add behaviour, because several apps can extend the same class without fighting.
  • after_install and after_migrate for setup code.

When a hook doesn’t fire

Check the dotted path first. A typo in acme_custom.sales_order.check_credit_hold fails at runtime, not when the file loads. Then check that the app is actually installed on that site with bench --site <site> list-apps, since installing on the bench alone isn’t enough. After that, clear the cache and restart.

If you’re weighing a hook in a custom Frappe app against a Server Script for the same rule, we lay out where each one fits in Server Scripts vs a custom app. For larger builds, see ERPNext custom module development.

Sources

The official documentation and source code this page was checked against.

  1. 01 Hooks, Frappe Framework documentation docs.frappe.io
  2. 02 Controllers, Frappe Framework documentation docs.frappe.io
  3. 03 frappe/__init__.py, get_hooks (version-15), frappe/frappe on GitHub github.com

Where hooks.py comes up

More ERPNext terms

Your next module is one paragraph away

Write it the way you’d explain it to a new hire. We’ll turn it into an app you can read, test and install.

ERPNext v16

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.