$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-syntax-customappUse when building Frappe custom apps from scratch. Covers app structure, pyproject.toml configuration, module creation, patches, and fixtures for v14/v15/v16. Prevents common mistakes with app scaffolding and module organization. Keywords: custom app, bench new-app, pyproject.tom
| 1 | # Frappe Custom App Syntax |
| 2 | |
| 3 | Deterministic syntax reference for building Frappe custom apps — scaffolding, configuration, modules, patches, and fixtures. |
| 4 | |
| 5 | ## Decision Tree |
| 6 | |
| 7 | ``` |
| 8 | What do you need? |
| 9 | ├─ Brand new app from scratch → bench new-app |
| 10 | ├─ Extend existing ERPNext behavior → bench new-app + required_apps = ["frappe", "erpnext"] |
| 11 | ├─ Install existing app from Git → bench get-app <url> |
| 12 | └─ Add functionality to an installed app |
| 13 | ├─ New data model → Add module to modules.txt + create DocType |
| 14 | ├─ New fields on existing DocType → Fixtures (Custom Field) |
| 15 | ├─ Modify field properties → Fixtures (Property Setter) |
| 16 | └─ Data migration → Patch in patches.txt |
| 17 | |
| 18 | New app vs extend existing? |
| 19 | ├─ Independent functionality → New app |
| 20 | ├─ Tightly coupled to one app → New app with required_apps dependency |
| 21 | └─ Small customization (fields, properties) → Extend via fixtures in existing custom app |
| 22 | ``` |
| 23 | |
| 24 | ## Creating an App |
| 25 | |
| 26 | ```bash |
| 27 | # Create new app (interactive prompts for title, description, publisher, etc.) |
| 28 | bench new-app my_custom_app |
| 29 | |
| 30 | # Install on site |
| 31 | bench --site mysite install-app my_custom_app |
| 32 | |
| 33 | # Get existing app from Git |
| 34 | bench get-app https://github.com/org/my_custom_app |
| 35 | |
| 36 | # Build frontend assets |
| 37 | bench build --app my_custom_app |
| 38 | |
| 39 | # Run migrations (patches + fixtures + schema sync) |
| 40 | bench --site mysite migrate |
| 41 | ``` |
| 42 | |
| 43 | ## App Directory Structure |
| 44 | |
| 45 | ### [v15+] pyproject.toml (Primary) |
| 46 | |
| 47 | ``` |
| 48 | apps/my_custom_app/ |
| 49 | ├── pyproject.toml # Build configuration (flit) |
| 50 | ├── README.md |
| 51 | ├── my_custom_app/ # Inner Python package |
| 52 | │ ├── __init__.py # MUST contain __version__ |
| 53 | │ ├── hooks.py # Frappe integration hooks |
| 54 | │ ├── modules.txt # Module registration |
| 55 | │ ├── patches.txt # Migration scripts |
| 56 | │ ├── patches/ # Patch files |
| 57 | │ │ └── __init__.py |
| 58 | │ ├── my_custom_app/ # Default module (same name as app) |
| 59 | │ │ ├── __init__.py |
| 60 | │ │ └── doctype/ |
| 61 | │ ├── public/ # Static assets → /assets/my_custom_app/ |
| 62 | │ │ ├── css/ |
| 63 | │ │ └── js/ |
| 64 | │ ├── templates/ # Jinja templates |
| 65 | │ │ └── includes/ |
| 66 | │ └── www/ # Portal pages (URL = directory path) |
| 67 | └── .git/ |
| 68 | ``` |
| 69 | |
| 70 | ### [v14] setup.py (Legacy) |
| 71 | |
| 72 | ``` |
| 73 | apps/my_custom_app/ |
| 74 | ├── setup.py # Build configuration (setuptools) |
| 75 | ├── MANIFEST.in |
| 76 | ├── requirements.txt # Python dependencies |
| 77 | ├── dev-requirements.txt # Dev dependencies (developer_mode only) |
| 78 | ├── package.json # Node dependencies |
| 79 | ├── my_custom_app/ |
| 80 | │ ├── __init__.py |
| 81 | │ ├── hooks.py |
| 82 | │ ├── modules.txt |
| 83 | │ ├── patches.txt |
| 84 | │ └── [same inner structure as v15] |
| 85 | └── .git/ |
| 86 | ``` |
| 87 | |
| 88 | ## Critical Files |
| 89 | |
| 90 | ### __init__.py (REQUIRED) |
| 91 | |
| 92 | ```python |
| 93 | # my_custom_app/__init__.py |
| 94 | __version__ = "0.0.1" |
| 95 | ``` |
| 96 | |
| 97 | **CRITICAL**: Without `__version__`, the flit build FAILS and the app CANNOT be installed. |
| 98 | |
| 99 | ### pyproject.toml [v15+] |
| 100 | |
| 101 | ```toml |
| 102 | [build-system] |
| 103 | requires = ["flit_core >=3.4,<4"] |
| 104 | build-backend = "flit_core.buildapi" |
| 105 | |
| 106 | [project] |
| 107 | name = "my_custom_app" |
| 108 | authors = [ |
| 109 | { name = "Your Company", email = "dev@example.com" } |
| 110 | ] |
| 111 | description = "Description of your app" |
| 112 | requires-python = ">=3.10" |
| 113 | readme = "README.md" |
| 114 | dynamic = ["version"] |
| 115 | dependencies = [] # Python packages ONLY — NEVER Frappe/ERPNext |
| 116 | |
| 117 | [tool.bench.frappe-dependencies] |
| 118 | frappe = ">=15.0.0,<16.0.0" |
| 119 | erpnext = ">=15.0.0,<16.0.0" # Only if app extends ERPNext |
| 120 | ``` |
| 121 | |
| 122 | **CRITICAL rules for pyproject.toml**: |
| 123 | - `name` MUST match the inner directory name exactly |
| 124 | - `dynamic = ["version"]` is REQUIRED — flit reads `__version__` from `__init__.py` |
| 125 | - NEVER put `frappe` or `erpnext` in `[project] dependencies` (they are not on PyPI) |
| 126 | - ALWAYS put Frappe app dependencies in `[tool.bench.frappe-dependencies]` |
| 127 | |
| 128 | ### setup.py [v14] (Legacy) |
| 129 | |
| 130 | ```python |
| 131 | from setuptools import setup, find_packages |
| 132 | |
| 133 | setup( |
| 134 | name="my_custom_app", |
| 135 | version="0.0.1", |
| 136 | description="Description of your app", |
| 137 | author="Your Company", |
| 138 | author_email="dev@example.com", |
| 139 | packages=find_packages(), |
| 140 | zip_safe=False, |
| 141 | include_package_data=True, |
| 142 | install_requires=[], |
| 143 | ) |
| 144 | ``` |
| 145 | |
| 146 | ### hooks.py (Minimal Skeleton) |
| 147 | |
| 148 | ```python |
| 149 | app_name = "my_custom_app" |
| 150 | app_title = "My Custom App" |
| 151 | app_publisher = "Your Company" |
| 152 | app_description = "Description" |
| 153 | app_email = "dev@examp |