Framework Git

Framework Git

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

Hooks

hooks.py is the control panel of a Frappe app. It's a flat Python file of module-level variables that the framework reads to wire your app into the system. You use it to react to document events, schedule jobs, override classes, and inject assets. Every app has one at <app>/<app>/hooks.py.

Defining hooks in your app

Frappe imports hooks.py and reads its module-level variables, so a hook is just a top-level assignment. There is no registration function to call.

A hook value is one of three shapes:

  • a string dotted path to a function, like "library.install.after_install"
  • a list of dotted paths
  • a dict that maps a key (often a doctype) to one or more dotted paths
# single function
after_install = "library.install.after_install"

# list of functions
before_request = ["library.startup.before_request"]

# dict keyed by doctype
doc_events = {
    "Library Loan": {
        "on_submit": "library.events.loan.on_submit",
    },
}

Hooks are additive across apps: when multiple installed apps define the same hook, Frappe merges them. So you never edit another app's hooks.py to extend it. Define the same hook in your own app and Frappe combines the values. Read the merged result for any hook with frappe.get_hooks("hook_name").

After changing hooks.py, run bench migrate (or at least bench clear-cache) so the new configuration is picked up.

App metadata

The top of the file identifies the app:

app_name = "library"
app_title = "Library"
app_publisher = "Acme Inc."
app_description = "Manage books and loans"
app_license = "MIT"

Document events

doc_events is the most-used hook. It binds functions to lifecycle events of a doctype, exactly like controller methods, but without modifying that doctype's controller. This is how you react to another app's doctypes.

doc_events = {
    "Library Loan": {
        "on_submit": "library.events.loan.on_submit",
        "on_cancel": "library.events.loan.on_cancel",
    },
    # apply to every doctype with "*"
    "*": {
        "validate": "library.events.audit.log_change",
    },
}

The handler receives the document and the event name:

# library/events/loan.py
def on_submit(doc, method):
    frappe.msgprint(f"Loan {doc.name} issued")

Supported events mirror the lifecycle: before_insert, after_insert, before_validate, validate, before_save, on_update, before_submit, on_submit, before_cancel, on_cancel, before_update_after_submit, on_update_after_submit, before_rename, after_rename, on_trash, after_delete, and on_change.

Scheduler events

scheduler_events registers functions to run on a schedule (the scheduler must be enabled for the site). Use the named frequencies or a cron expression:

scheduler_events = {
    "daily": [
        "library.tasks.send_due_reminders",
    ],
    "hourly": [
        "library.tasks.sync_catalog",
    ],
    "cron": {
        "0/15 * * * *": [
            "library.tasks.poll_returns",
        ],
    },
}

Available frequencies include all (every scheduler tick), hourly, daily, weekly, monthly, their _long variants (run on the long-running worker), the _maintenance variants, and cron for arbitrary expressions. See Background Jobs for how these execute.

Overriding classes and methods

override_doctype_class

This is an advanced hook. Reach for doc_events first, and only override the class when you need to change methods those hooks can't reach.

Replace a DocType's controller class with your own subclass. Always extend the base controller class and call super() so the original behavior still runs. This is useful for changing behavior of a DocType you don't own:

override_doctype_class = {
    "ToDo": "library.overrides.todo.CustomToDo",
}
# library/overrides/todo.py
from frappe.desk.doctype.todo.todo import ToDo

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

override_whitelisted_methods

Redirect calls to a whitelisted method to a replacement of yours:

override_whitelisted_methods = {
    "frappe.client.get_count": "library.overrides.get_count",
}

See Whitelisted Methods.

Permission hooks

These extend access control. See Permissions in code for how they're applied.

# add SQL conditions to list queries (row-level filtering)
permission_query_conditions = {
    "Library Loan": "library.permissions.loan_query_conditions",
}

# custom per-document permission check
has_permission = {
    "Library Loan": "library.permissions.has_loan_permission",
}
# library/permissions.py
def loan_query_conditions(user=None, doctype=None):
    user = user or frappe.session.user
    return f"`tabLibrary Loan`.owner = {frappe.db.escape(user)}"

def has_loan_permission(doc, ptype, user, debug=False):
    return doc.owner == user

Request and job hooks

Run code at the boundaries of every request and background job:

before_request = ["library.startup.before_request"]
after_request = ["library.startup.after_request"]

before_job = ["library.startup.before_job"]
after_job = ["library.startup.after_job"]

Login and session hooks

on_session_creation = ["library.auth.on_session_creation"]
on_login = ["library.auth.on_login"]
on_logout = ["library.auth.on_logout"]

# add data to the JS bootinfo sent to the desk on load
extend_bootinfo = ["library.boot.boot_session"]

on_login, on_session_creation, and on_logout are each called with the login_manager. extend_bootinfo is called with the bootinfo dict, which you mutate in place; the result is available client-side as frappe.boot.

# library/boot.py
def boot_session(bootinfo):
    bootinfo.my_global_key = "my_global_value"

Authentication hooks

auth_hooks run during request authentication, before the request is handled. Use them to read a custom header or token, verify it, and map the request to a user with frappe.set_user(). They take no arguments.

auth_hooks = ["library.auth.validate_custom_jwt"]

Do not raise if verification fails. Return without setting a user and the request stays a Guest request, so other auth hooks still get a chance to run.

Assets

Inject JavaScript and CSS into the Desk, the website, or specific DocType forms:

app_include_js = ["library.bundle.js"]    # desk
app_include_css = ["library.bundle.css"]
web_include_js = ["library_web.js"]        # website pages
web_include_css = ["library_web.css"]

doctype_js = {"Library Loan": "public/js/library_loan.js"}     # form view
page_js = {"background_jobs": "public/js/custom_background_jobs.js"}  # desk page
webform_include_js = {"ToDo": "public/js/custom_todo.js"}      # standard web form

Jinja and queries

# expose methods/filters to Jinja templates and print formats
jinja = {
    "methods": ["library.utils.jinja_methods"],
    "filters": ["library.utils.jinja_filters"],
}

# replace the default link-field search query for a doctype
standard_queries = {"Library Book": "library.queries.book_query"}

Website hooks

These hooks control how portal (website) pages render and route.

Context

When a portal page renders, Frappe builds a context dict of values the template can use. website_context is a flat dict of static overrides. update_website_context points to a function for dynamic changes; it gets the context dict and can mutate it or return a dict to merge.

website_context = {"favicon": "/assets/library/img/favicon.png"}
update_website_context = "library.website.update_context"
# library/website.py
def update_context(context):
    context.my_key = "my_value"

Redirects and route rules

website_redirects maps source routes to targets. The source can be a plain path or a regex, and the target can reference capture groups.

website_redirects = [
    {"source": "/compare", "target": "/comparison"},
    {"source": r"/docs(/.*)?", "target": r"https://docs.example.com/\1"},
]

website_route_rules maps a URL pattern to a controller path. Use it for clean, dynamic URLs.

website_route_rules = [
    {"from_route": "/projects/<name>", "to_route": "library/projects/project"},
]

The controller reads the matched parameter from frappe.form_dict:

# library/projects/project.py
def get_context(context):
    context.project = frappe.get_doc("Project", frappe.form_dict.name)

Home page

The root URL (/) renders www/index by default. Override it, top to bottom in increasing priority:

# static override
home_page = "homepage"

# per-role override
role_home_page = {"Customer": "orders", "Supplier": "bills"}

# full control: function receives the user, returns a route
get_website_user_home_page = "library.website.get_home_page"

If more than one is set, get_website_user_home_page wins over role_home_page, which wins over home_page.

Portal sidebar

Some portal views show a sidebar of links. portal_menu_items are defined in code and fixed. standard_portal_menu_items sync to Portal Settings, where a System User can edit them later.

standard_portal_menu_items = [
    {"title": "Orders", "route": "/orders", "role": "Customer"},
]

Clearing website cache

Frappe caches rendered web pages. website_clear_cache runs when that cache is cleared. The function gets a path: a route when one page is cleared, or None when all pages are cleared.

website_clear_cache = "library.website.clear_cache"

User data privacy

Frappe ships personal data download and deletion. user_data_fields declares which doctypes hold personal data so those flows can find and redact it. Each entry is a dict:

user_data_fields = [
    {"doctype": "Access Log", "strict": True},
    {"doctype": "Contact", "filter_by": "email_id", "rename": True},
    {"doctype": "File", "filter_by": "attached_to_name", "redact_fields": ["file_name", "file_url"]},
    {"doctype": "Email Unsubscribe", "filter_by": "email", "partial": True},
]
Key Meaning
doctype The doctype that holds user data.
filter_by Field used to find the user's records. Defaults to owner.
redact_fields Fields to redact. If unset, redacts personal data from all text fields.
partial Redact the user's name and username from all text fields.
rename Rename the document to anonymize it when its name contains user data.
strict Redact data from every record of the doctype, not just ones the user owns.

Download only uses doctype and filter_by.

Fixtures

Fixtures let you ship database records as part of your app. They're useful for records you create through the Desk but want to version with code, like Custom Fields, Roles, or Workflows.

The fixtures hook lists the doctypes (and optional filters) to include:

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

An entry is either a doctype name (export every record) or a dict with dt and filters (or or_filters) to export a subset.

Export the records to JSON with:

bench --site <site> export-fixtures --app library

This writes one file per doctype to <app>/<app>/fixtures/, for example fixtures/custom_field.json. Commit those files.

On bench migrate, Frappe imports every JSON file under fixtures/ for each installed app, overwriting matching records on the target site. So fixtures sync one way: export from where you author them, then migrate everywhere else to apply them. If a fixture's doctype doesn't exist on the site, that file is skipped.

Install and migrate hooks

before_install = "library.install.before_install"
after_install = "library.install.after_install"

before_migrate = "library.migrate.before_migrate"
after_migrate = "library.migrate.after_migrate"

before_uninstall = "library.install.before_uninstall"
after_uninstall = "library.install.after_uninstall"

before_uninstall/after_uninstall run when the app is removed from a site with bench uninstall-app.

after_sync runs after the app's fixtures are synced. before_tests runs once before the test suite starts, which is where you seed data your tests depend on.

after_sync = "library.install.after_sync"
before_tests = "library.tests.before_tests"

Other app install hooks

You can also react to installation and uninstallation of other apps on a site, useful when toggling functionality when a certain app (e.g. integrations) is installed.

before_app_install = "library.integrations.before_app_install"
after_app_install = "library.integrations.after_app_install"

before_app_uninstall = "library.integrations.before_app_uninstall"
after_app_uninstall = "library.integrations.after_app_uninstall"

File hooks

These override how user-uploaded files are stored, so you can write to a CDN or object store instead of the local disk.

before_write_file = "library.overrides.file.before_write"
write_file = "library.overrides.file.write_file"
delete_file_data_content = "library.overrides.file.delete_file"

before_write_file runs before a file is saved. write_file replaces the save itself. delete_file_data_content replaces deletion.

Discovering hooks

The framework's own hooks.py is the most complete reference for what each hook expects, so read frappe/hooks.py in the source. To see the merged value across all installed apps for any hook:

frappe.get_hooks("doc_events")
frappe.get_hooks("scheduler_events")

Hook index

The commonly used hooks, alphabetically, with the section that covers each.

Hook Section
after_app_install Other app install hooks
after_app_uninstall Other app install hooks
after_install Install and migrate hooks
after_job Request and job hooks
after_migrate Install and migrate hooks
after_request Request and job hooks
after_sync Install and migrate hooks
after_uninstall Install and migrate hooks
app_include_css Assets
app_include_js Assets
app_name, app_title, ... App metadata
auth_hooks Authentication hooks
before_app_install Other app install hooks
before_app_uninstall Other app install hooks
before_install Install and migrate hooks
before_job Request and job hooks
before_migrate Install and migrate hooks
before_request Request and job hooks
before_tests Install and migrate hooks
before_uninstall Install and migrate hooks
before_write_file File hooks
delete_file_data_content File hooks
doc_events Document events
doctype_js Assets
extend_bootinfo Login and session hooks
fixtures Fixtures
get_website_user_home_page Website hooks
has_permission Permission hooks
home_page Website hooks
jinja Jinja and queries
on_login Login and session hooks
on_logout Login and session hooks
on_session_creation Login and session hooks
override_doctype_class override_doctype_class
override_whitelisted_methods override_whitelisted_methods
page_js Assets
permission_query_conditions Permission hooks
portal_menu_items Website hooks
role_home_page Website hooks
scheduler_events Scheduler events
standard_portal_menu_items Website hooks
standard_queries Jinja and queries
update_website_context Website hooks
user_data_fields User data privacy
web_include_css Assets
web_include_js Assets
webform_include_js Assets
website_clear_cache Website hooks
website_context Website hooks
website_redirects Website hooks
website_route_rules Website hooks
write_file File hooks

See also

Last updated 2 hours ago
Was this helpful?
Thanks!