Working with hooks.py event patterns and configurations in Frappe.
hooks.py is the app's central configuration file. It tells Frappe how to integrate the app.
File: apps/<app>/<app>/hooks.py
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
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).
Run Python functions when any DocType triggers lifecycle events:
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:
def validate(doc, method):
if doc.amount > 10000:
frappe.throw("Amount exceeds limit")
scheduler_events = {
"daily": ["myapp.tasks.daily_cleanup"],
"hourly": ["myapp.tasks.sync_data"],
"cron": {
"*/5 * * * *": ["myapp.tasks.check_status"]
}
}
See background-jobs.md for details.
app_include_css = "/assets/myapp/css/myapp.css"
app_include_js = "/assets/myapp/js/myapp.js"
override_doctype_class = {
"ToDo": "myapp.overrides.CustomToDo"
}
The override class MUST inherit from the original:
# 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.
# Override standard whitelisted methods, use sparingly
override_whitelisted_methods = {
"frappe.client.get_count": "myapp.api.custom_get_count"
}
fixtures = [
"Custom Field",
{"dt": "Role", "filters": [["name", "in", ["Expense Manager"]]]},
]
Export: bench --site <site> export-fixtures --app <app-name>
website_route_rules = [
{"from_route": "/expenses", "to_route": "expenses/index"},
{"from_route": "/expenses/<path:name>", "to_route": "expenses/view"},
]
overrides/, tasks.py)# 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",
}