frappe-app-development

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frappe App Development

Frappe应用开发

Scaffold, structure, and architect custom Frappe applications with production-grade patterns.
采用生产级模式搭建、构建并架构自定义Frappe应用。

When to use

使用场景

  • Creating a new custom Frappe app from scratch
  • Setting up app architecture (modules, services, utils)
  • Configuring
    hooks.py
    for events, scheduler, overrides
  • Implementing background jobs and async processing
  • Building service layers and domain logic patterns
  • Adding caching, logging, error handling utilities
  • Preparing apps for production deployment
  • Managing translations, versioning, and packaging
  • 从零开始创建自定义Frappe应用
  • 设置应用架构(模块、服务、工具类)
  • 配置
    hooks.py
    以处理事件、调度器、重写逻辑
  • 实现后台任务与异步处理
  • 构建服务层与领域逻辑模式
  • 添加缓存、日志、错误处理工具
  • 为生产部署准备应用
  • 管理翻译、版本控制与打包

Inputs required

所需输入

  • App name and purpose
  • Target Frappe version (v13+, v15+, v16+)
  • Module structure (which DocTypes, APIs, services)
  • Whether hooks/overrides of other apps are needed
  • Background job requirements
  • Production readiness needs
  • 应用名称与用途
  • 目标Frappe版本(v13+、v15+、v16+)
  • 模块结构(包含哪些DocTypes、API、服务)
  • 是否需要重写其他应用的hooks
  • 后台任务需求
  • 生产环境就绪要求

Procedure

实施步骤

0) Scaffold the app

0) 搭建应用脚手架

bash
undefined
bash
undefined

Create the app

创建应用

bench new-app my_app
bench new-app my_app

Install on site

在站点安装应用

bench --site mysite.local install-app my_app
bench --site mysite.local install-app my_app

Verify developer mode

验证开发者模式

bench --site mysite.local console
frappe.conf.developer_mode # Must be True
undefined
bench --site mysite.local console
frappe.conf.developer_mode # 必须为True
undefined

1) Plan app structure

1) 规划应用结构

Follow the domain architecture pattern — keep DocType controllers thin and business logic in service modules:
my_app/
├── my_app/
│   ├── __init__.py
│   ├── hooks.py              # App hooks and configuration
│   ├── api.py                # Public API surface (RPC endpoints)
│   ├── services/             # Business logic modules
│   │   └── billing.py
│   ├── utils/                # Cross-cutting utilities
│   │   ├── cache.py
│   │   ├── errors.py
│   │   ├── logging.py
│   │   ├── permissions.py
│   │   └── validation.py
│   ├── background_jobs/      # Async job handlers
│   │   └── export_job.py
│   ├── integrations/         # External system connectors
│   │   └── payment_gateway.py
│   ├── my_module/
│   │   ├── doctype/
│   │   │   └── my_doc/
│   │   │       ├── my_doc.json
│   │   │       ├── my_doc.py
│   │   │       ├── my_doc.js
│   │   │       └── test_my_doc.py
│   │   ├── report/
│   │   └── dashboard/
│   └── translations/
│       ├── en.csv
│       └── fr.csv
├── setup.py
└── README.md
Use the mini-app-template in
assets/mini-app-template/
as a starting scaffold.
遵循领域架构模式——保持DocType控制器精简,将业务逻辑放在服务模块中:
my_app/
├── my_app/
│   ├── __init__.py
│   ├── hooks.py              # 应用hooks与配置
│   ├── api.py                # 公开API接口(RPC端点)
│   ├── services/             # 业务逻辑模块
│   │   └── billing.py
│   ├── utils/                # 横切工具类
│   │   ├── cache.py
│   │   ├── errors.py
│   │   ├── logging.py
│   │   ├── permissions.py
│   │   └── validation.py
│   ├── background_jobs/      # 异步任务处理器
│   │   └── export_job.py
│   ├── integrations/         # 外部系统连接器
│   │   └── payment_gateway.py
│   ├── my_module/
│   │   ├── doctype/
│   │   │   └── my_doc/
│   │   │       ├── my_doc.json
│   │   │       ├── my_doc.py
│   │   │       ├── my_doc.js
│   │   │       └── test_my_doc.py
│   │   ├── report/
│   │   └── dashboard/
│   └── translations/
│       ├── en.csv
│       └── fr.csv
├── setup.py
└── README.md
可使用
assets/mini-app-template/
中的迷你应用模板作为初始脚手架。

2) Configure hooks.py

2) 配置hooks.py

python
undefined
python
undefined

hooks.py

hooks.py

app_name = "my_app" app_title = "My App" app_publisher = "My Company"
app_name = "my_app" app_title = "My App" app_publisher = "My Company"

DocType lifecycle events

DocType生命周期事件

doc_events = { "Sales Order": { "on_submit": "my_app.services.billing.on_order_submit", "on_cancel": "my_app.services.billing.on_order_cancel", }, "*": { "after_insert": "my_app.utils.logging.log_creation", } }
doc_events = { "Sales Order": { "on_submit": "my_app.services.billing.on_order_submit", "on_cancel": "my_app.services.billing.on_order_cancel", }, "*": { "after_insert": "my_app.utils.logging.log_creation", } }

Scheduled tasks

定时任务

scheduler_events = { "daily": [ "my_app.background_jobs.cleanup.run_daily_cleanup" ], "cron": { "0 */6 * * *": [ "my_app.background_jobs.sync.sync_external_data" ] } }
scheduler_events = { "daily": [ "my_app.background_jobs.cleanup.run_daily_cleanup" ], "cron": { "0 */6 * * *": [ "my_app.background_jobs.sync.sync_external_data" ] } }

Client-side script injection

客户端脚本注入

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

Override another app's controller (use sparingly)

重写其他应用的控制器(谨慎使用)

override_doctype_class = {

override_doctype_class = {

"ToDo": "my_app.overrides.custom_todo.CustomToDo"

"ToDo": "my_app.overrides.custom_todo.CustomToDo"

}

}

Extend controller without full replacement (v16+, preferred)

无需完全替换即可扩展控制器(v16+,推荐方式)

extend_doctype_class = {

extend_doctype_class = {

"ToDo": "my_app.overrides.todo_extension.TodoExtension"

"ToDo": "my_app.overrides.todo_extension.TodoExtension"

}

}

Override whitelisted methods

重写白名单方法

override_whitelisted_methods = {

override_whitelisted_methods = {

"frappe.client.get_list": "my_app.overrides.custom_get_list"

"frappe.client.get_list": "my_app.overrides.custom_get_list"

}

}

undefined
undefined

3) Implement service layer

3) 实现服务层

Keep DocType controllers thin — delegate business logic to services:
python
undefined
保持DocType控制器精简——将业务逻辑委托给服务模块:
python
undefined

my_app/services/billing.py

my_app/services/billing.py

import frappe
def on_order_submit(doc, method): """Handle order submission — called via doc_events hook.""" if doc.grand_total > 10000: create_approval_request(doc) generate_invoice(doc)
def generate_invoice(order): """Create invoice from submitted order.""" invoice = frappe.get_doc({ "doctype": "Sales Invoice", "customer": order.customer, "items": [ {"item_code": i.item_code, "qty": i.qty, "rate": i.rate} for i in order.items ] }) invoice.insert() invoice.submit() return invoice
undefined
import frappe
def on_order_submit(doc, method): """处理订单提交——通过doc_events钩子调用。""" if doc.grand_total > 10000: create_approval_request(doc) generate_invoice(doc)
def generate_invoice(order): """从已提交的订单创建发票。""" invoice = frappe.get_doc({ "doctype": "Sales Invoice", "customer": order.customer, "items": [ {"item_code": i.item_code, "qty": i.qty, "rate": i.rate} for i in order.items ] }) invoice.insert() invoice.submit() return invoice
undefined

4) Set up background jobs

4) 设置后台任务

python
undefined
python
undefined

my_app/background_jobs/export_job.py

my_app/background_jobs/export_job.py

import frappe
def enqueue_export(filters): """Enqueue a long-running export job.""" frappe.enqueue( "my_app.background_jobs.export_job.run_export", filters=filters, queue="long", timeout=600, is_async=True )
def run_export(filters): """Execute the export — runs in background worker.""" data = frappe.get_all("Sales Order", filters=filters, fields=["*"]) # Process data... frappe.publish_realtime("export_complete", {"count": len(data)})
undefined
import frappe
def enqueue_export(filters): """将耗时较长的导出任务加入队列。""" frappe.enqueue( "my_app.background_jobs.export_job.run_export", filters=filters, queue="long", timeout=600, is_async=True )
def run_export(filters): """执行导出任务——在后台工作进程中运行。""" data = frappe.get_all("Sales Order", filters=filters, fields=["*"]) # 处理数据... frappe.publish_realtime("export_complete", {"count": len(data)})
undefined

5) Add cross-cutting utilities

5) 添加横切工具类

python
undefined
python
undefined

my_app/utils/cache.py

my_app/utils/cache.py

import frappe
def get_cached_settings(key): """Cache expensive settings lookups.""" value = frappe.cache().get_value(f"my_app:{key}") if value is None: value = frappe.db.get_single_value("My Settings", key) frappe.cache().set_value(f"my_app:{key}", value) return value
def invalidate_cache(key): frappe.cache().delete_value(f"my_app:{key}")

```python
import frappe
def get_cached_settings(key): """缓存耗时的配置查询。""" value = frappe.cache().get_value(f"my_app:{key}") if value is None: value = frappe.db.get_single_value("My Settings", key) frappe.cache().set_value(f"my_app:{key}", value) return value
def invalidate_cache(key): frappe.cache().delete_value(f"my_app:{key}")

```python

my_app/utils/errors.py

my_app/utils/errors.py

import frappe
def api_error(message, status_code=400, exc=None): """Consistent error response for API endpoints.""" frappe.local.response["http_status_code"] = status_code frappe.throw(message, exc or frappe.ValidationError)
undefined
import frappe
def api_error(message, status_code=400, exc=None): """为API端点提供一致的错误响应。""" frappe.local.response["http_status_code"] = status_code frappe.throw(message, exc or frappe.ValidationError)
undefined

6) Handle translations

6) 处理翻译

python
undefined
python
undefined

In Python code

在Python代码中

frappe._("Hello World") # Mark string for translation
frappe._("Hello World") # 标记字符串以支持翻译

In JavaScript

在JavaScript中

__("Hello World") # Mark string for translation

```bash
__("Hello World") # 标记字符串以支持翻译

```bash

Translation CSV files go in my_app/translations/

翻译CSV文件放在my_app/translations/目录下

e.g., my_app/translations/fr.csv:

例如,my_app/translations/fr.csv:

Hello World,Bonjour le monde

Hello World,Bonjour le monde

undefined
undefined

7) Version compatibility

7) 版本兼容性

FeatureMinimum Version
extend_doctype_class
Frappe v16+
REST API v2 (
/api/v2/
)
Frappe v15+
Token-based authFrappe v11.0.3+
When targeting multiple versions, guard version-specific features:
python
import frappe

if hasattr(frappe, 'extend_doctype_class'):
    # v16+ pattern
    pass
else:
    # Fallback for older versions
    pass
功能最低版本
extend_doctype_class
Frappe v16+
REST API v2 (
/api/v2/
)
Frappe v15+
基于令牌的认证Frappe v11.0.3+
当适配多个版本时,需对版本特定功能进行防护:
python
import frappe

if hasattr(frappe, 'extend_doctype_class'):
    # v16+模式
    pass
else:
    # 旧版本兼容方案
    pass

Verification

验证

  • App installs without errors:
    bench --site <site> install-app my_app
  • Hooks fire correctly (check scheduler logs, doc events)
  • Background jobs enqueue and complete
  • bench --site <site> migrate
    succeeds
  • Tests pass:
    bench --site <site> run-tests --app my_app
  • 应用安装无错误:
    bench --site <site> install-app my_app
  • Hooks触发正常(检查调度器日志、文档事件)
  • 后台任务能正常加入队列并完成
  • bench --site <site> migrate
    执行成功
  • 测试通过:
    bench --site <site> run-tests --app my_app

Failure modes / debugging

故障模式与调试

  • App not found: Check
    apps.txt
    and
    sites/<site>/site_config.json
  • Hooks not firing: Verify
    hooks.py
    syntax; restart bench
  • Background jobs stuck: Check worker status with
    bench doctor
    ; verify Redis
  • Import errors: Ensure module paths in hooks match actual Python paths
  • Developer mode off: DocType changes won't export to files
  • 应用未找到:检查
    apps.txt
    sites/<site>/site_config.json
  • Hooks未触发:验证
    hooks.py
    语法;重启bench
  • 后台任务停滞:使用
    bench doctor
    检查工作进程状态;验证Redis
  • 导入错误:确保hooks中的模块路径与实际Python路径匹配
  • 开发者模式关闭:DocType变更不会导出到文件

Escalation

问题升级

  • For DocType creation details →
    frappe-doctype-development
  • For API endpoint patterns →
    frappe-api-development
  • For Desk UI customization →
    frappe-desk-customization
  • For Frappe UI frontends →
    frappe-frontend-development
  • For print formats and Jinja →
    frappe-printing-templates
  • For reports →
    frappe-reports
  • For web forms →
    frappe-web-forms
  • For testing →
    frappe-testing
  • For enterprise architecture →
    frappe-enterprise-patterns
  • For Docker/FM environments →
    frappe-manager
  • DocType创建细节 →
    frappe-doctype-development
  • API端点模式 →
    frappe-api-development
  • Desk UI自定义 →
    frappe-desk-customization
  • Frappe UI前端 →
    frappe-frontend-development
  • 打印格式与Jinja →
    frappe-printing-templates
  • 报表 →
    frappe-reports
  • Web表单 →
    frappe-web-forms
  • 测试 →
    frappe-testing
  • 企业架构 →
    frappe-enterprise-patterns
  • Docker/FM环境 →
    frappe-manager

References

参考资料

  • references/app-development.md — End-to-end app architecture
  • references/version-compat.md — Version compatibility notes
  • references/translations.md — Multi-language support
  • references/app-development.md — 端到端应用架构
  • references/version-compat.md — 版本兼容性说明
  • references/translations.md — 多语言支持

Cross-references (owned by specialized skills)

交叉参考(由专业技能模块维护)

  • hooks.py and extension points →
    frappe-doctype-development
    (hooks-extensions.md)
  • Python API reference →
    frappe-api-development
    (python-api.md)
  • hooks.py与扩展点 →
    frappe-doctype-development
    (hooks-extensions.md)
  • Python API参考 →
    frappe-api-development
    (python-api.md)

Guardrails

规范准则

  • Use Frappe UI for custom frontends: Never use vanilla JS, jQuery, or custom frameworks. Frappe UI (Vue 3 + TailwindCSS) is the ecosystem standard. See
    frappe-frontend-development
    for setup.
  • Follow CRM/Helpdesk patterns for CRUD apps: Follow
    frappe-ui-patterns
    skill for app shell, navigation, list views, and form layouts derived from official Frappe apps.
  • Follow naming conventions: App name must be lowercase with underscores, valid Python identifier
  • Use hooks.py for integrations: Never monkey-patch; use doc_events, scheduler_events, boot_session hooks
  • Keep hooks.py clean: Only configuration, no logic; import from modules
  • Maintain backwards compatibility: Use
    frappe.version
    checks for cross-version support
  • Export fixtures properly: Use
    fixtures
    in hooks.py for data that should sync with app
  • 使用Frappe UI开发自定义前端:禁止使用原生JS、jQuery或其他自定义框架。Frappe UI(Vue 3 + TailwindCSS)是生态系统标准。详见
    frappe-frontend-development
    的设置说明。
  • CRUD应用遵循CRM/Helpdesk模式:遵循
    frappe-ui-patterns
    技能模块的规范,采用官方Frappe应用衍生的应用外壳、导航、列表视图和表单布局。
  • 遵循命名规范:应用名称必须为小写并使用下划线,符合Python标识符规则
  • 使用hooks.py进行集成:禁止使用猴子补丁;使用doc_events、scheduler_events、boot_session等hooks
  • 保持hooks.py简洁:仅存放配置,不包含逻辑;从模块导入逻辑
  • 维护向后兼容性:使用
    frappe.version
    检查实现跨版本支持
  • 正确导出固定数据:使用hooks.py中的
    fixtures
    配置需随应用同步的数据

Common Mistakes

常见错误

MistakeWhy It FailsFix
App not in
installed_apps
App code not loadedRun
bench --site <site> install-app my_app
Wrong module path in hooksEvents don't fireVerify path matches actual
my_app/module/file.py
structure
Duplicate hook registrationsEvents fire multiple timesCheck hooks.py for duplicates; use list not repeated keys
Editing hooks.py without restartChanges not picked upRun
bench restart
after hooks.py changes
Missing
__init__.py
files
Module import errorsEnsure every directory has
__init__.py
Logic in hooks.pyHard to test, import errorsMove logic to separate modules, import in hooks
Building frontend with vanilla JS/jQueryInconsistent with ecosystemUse Frappe UI (Vue 3); see
frappe-frontend-development
Custom app shell for CRUD appsInconsistent UXFollow CRM/Helpdesk patterns for navigation and layouts
错误失败原因修复方案
应用不在
installed_apps
应用代码未加载执行
bench --site <site> install-app my_app
hooks中的模块路径错误事件无法触发验证路径与实际
my_app/module/file.py
结构匹配
hook重复注册事件多次触发检查hooks.py是否存在重复项;使用列表而非重复键
修改hooks.py后未重启变更未生效修改hooks.py后执行
bench restart
缺少
__init__.py
文件
模块导入错误确保每个目录都包含
__init__.py
在hooks.py中编写逻辑难以测试、易出现导入错误将逻辑移至独立模块,在hooks中导入
使用原生JS/jQuery开发前端与生态系统不一致使用Frappe UI(Vue 3);详见
frappe-frontend-development
为CRUD应用自定义应用外壳用户体验不一致遵循CRM/Helpdesk模式设计导航与布局