Skip to content
Skip to the article
In Frappe: 20 articles
Frappe

Project Structure & Hooks

Standard Frappe app layout, the main hooks.py settings, bundle naming, and post-migration scripts.

Updated
Tags
  • frappe
  • hooks
  • app-structure
  • python
Reading time
3 min

App Directory Layout

A Frappe app follows this standard structure:

myapp/
├── myapp/
│   ├── __init__.py
│   ├── hooks.py              # Central configuration file
│   ├── modules.txt            # List of modules (one per line)
│   ├── patches.txt            # Migration patches
│   ├── migrate.py             # Post-migration hook (optional)
│   ├── mymodule/
│   │   ├── doctype/
│   │   │   └── my_doctype/
│   │   │       ├── my_doctype.json    # DocType definition
│   │   │       ├── my_doctype.py      # Controller
│   │   │       ├── my_doctype.js      # Form script
│   │   │       ├── my_doctype_list.js # List view customization (optional)
│   │   │       └── test_my_doctype.py # Tests
│   │   ├── page/
│   │   │   └── my_page/
│   │   │       ├── my_page.json       # Page definition
│   │   │       ├── my_page.py         # Empty (required for registration)
│   │   │       └── my_page.js         # Page script (or built bundle)
│   │   ├── report/
│   │   │   └── my_report/
│   │   │       ├── my_report.json     # Report definition
│   │   │       ├── my_report.py       # Data logic
│   │   │       ├── my_report.js       # Client-side config
│   │   │       ├── my_report.sql      # External SQL (optional)
│   │   │       ├── my_report.css      # Report styles (optional)
│   │   │       └── columns.json       # External column defs (optional)
│   │   └── web_form/
│   │       └── my_form/
│   │           ├── my_form.json       # Web form definition
│   │           ├── my_form.py         # get_context() hook
│   │           └── my_form.js         # Client-side logic
│   ├── custom/                        # DocType overrides (see customizations-api.md)
│   │   ├── doctype_name.json
│   │   └── doctype_name.py
│   ├── public/
│   │   ├── js/
│   │   │   ├── app.bundle.js          # Auto-included desk JS bundle
│   │   │   └── my_control.bundle.js   # Named bundle (include via hooks)
│   │   └── dist/                      # Vite/Rollup build output
│   └── utils/                         # Shared utilities
├── pyproject.toml
├── package.json                       # Node dependencies (if using Vite/Rollup)
├── vite.config.ts                     # Vite build config (optional)
└── tsconfig.json                      # TypeScript config (optional)

hooks.py Reference

hooks.py is the central configuration file for a Frappe app. Key properties:

Basic App Info

app_name = "myapp"
app_title = "My App"
app_publisher = "Avunu LLC"
app_description = "Description"
app_license = "MIT"

export_python_type_annotations = True  # Enable DF.* type annotation system
required_apps = ["frappe", "erpnext"]  # App dependencies

Asset Inclusion

# Global desk JS/CSS (loaded on every desk page)
app_include_js = "myapp.app.bundle.js"
app_include_css = "myapp.app.bundle.css"

# Website-only assets
web_include_js = ["/assets/myapp/js/web.js"]
web_include_css = ["/assets/myapp/css/web.css"]

# Web form specific assets
webform_include_js = {"My Web Form": ["public/js/webform_helper.js"]}
webform_include_css = {"My Web Form": ["public/css/webform.css"]}

DocType Class Overrides

override_doctype_class = {
    "Item": "myapp.myapp.custom.item.Item",
    "BOM": "myapp.myapp.custom.bom.BOM",
}

Document Event Hooks

doc_events = {
    "Workday": {
        "before_save": "myapp.api.workday_update",
        "on_update": "myapp.api.workday_changed",
    },
    "*": {  # All doctypes
        "on_trash": "myapp.api.handle_trash",
    },
}

List View JS Overrides

doctype_list_js = {
    "Job Card": "public/js/job_card.js",
}

Post-Migration Hook

after_migrate = "myapp.migrate.after_migrate"

Bundle Naming Convention

Frappe's asset builder recognizes files by naming pattern:

PatternBehavior
*.bundle.jsProcessed through Frappe's esbuild bundler (resolves imports)
*.bundle.cssProcessed through Frappe's CSS bundler
app.bundle.jsSpecial: auto-included when app_include_js references it

Place bundles in <app>/public/js/ and reference them in hooks:

app_include_js = "myapp.app.bundle.js"  # → public/js/app.bundle.js

The app.bundle.js convention is particularly useful for injecting global behavior (custom views, field controllers) without explicitly including them per page.

Post-Migration Script

migrate.py runs after bench migrate completes:

from frappe.utils.nestedset import rebuild_tree
from frappe.utils.scheduler import activate_scheduler

def after_migrate() -> None:
    rebuild_tree("Item Group")  # Rebuild nested set tree
    activate_scheduler()         # Ensure scheduler is running

Register in hooks: after_migrate = "myapp.migrate.after_migrate".

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.