> Use 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 patches, fixtures, modules, app structure, app boilerplate, bench new-app example, module setup, patch example.
npx skills add https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-syntax-customapp
Deterministic syntax reference for building Frappe custom apps — scaffolding, configuration, modules, patches, and fixtures.
What do you need?
├─ Brand new app from scratch → bench new-app
├─ Extend existing ERPNext behavior → bench new-app + required_apps = ["frappe", "erpnext"]
├─ Install existing app from Git → bench get-app <url>
└─ Add functionality to an installed app
├─ New data model → Add module to modules.txt + create DocType
├─ New fields on existing DocType → Fixtures (Custom Field)
├─ Modify field properties → Fixtures (Property Setter)
└─ Data migration → Patch in patches.txt
New app vs extend existing?
├─ Independent functionality → New app
├─ Tightly coupled to one app → New app with required_apps dependency
└─ Small customization (fields, properties) → Extend via fixtures in existing custom app
# Create new app (interactive prompts for title, description, publisher, etc.)
bench new-app my_custom_app
# Install on site
bench --site mysite install-app my_custom_app
# Get existing app from Git
bench get-app https://github.com/org/my_custom_app
# Build frontend assets
bench build --app my_custom_app
# Run migrations (patches + fixtures + schema sync)
bench --site mysite migrate
apps/my_custom_app/
├── pyproject.toml # Build configuration (flit)
├── README.md
├── my_custom_app/ # Inner Python package
│ ├── __init__.py # MUST contain __version__
│ ├── hooks.py # Frappe integration hooks
│ ├── modules.txt # Module registration
│ ├── patches.txt # Migration scripts
│ ├── patches/ # Patch files
│ │ └── __init__.py
│ ├── my_custom_app/ # Default module (same name as app)
│ │ ├── __init__.py
│ │ └── doctype/
│ ├── public/ # Static assets → /assets/my_custom_app/
│ │ ├── css/
│ │ └── js/
│ ├── templates/ # Jinja templates
│ │ └── includes/
│ └── www/ # Portal pages (URL = directory path)
└── .git/
apps/my_custom_app/
├── setup.py # Build configuration (setuptools)
├── MANIFEST.in
├── requirements.txt # Python dependencies
├── dev-requirements.txt # Dev dependencies (developer_mode only)
├── package.json # Node dependencies
├── my_custom_app/
│ ├── __init__.py
│ ├── hooks.py
│ ├── modules.txt
│ ├── patches.txt
│ └── [same inner structure as v15]
└── .git/
# my_custom_app/__init__.py
__version__ = "0.0.1"
CRITICAL: Without __version__, the flit build FAILS and the app CANNOT be installed.
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "my_custom_app"
authors = [
{ name = "Your Company", email = "[email protected]" }
]
description = "Description of your app"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
dependencies = [] # Python packages ONLY — NEVER Frappe/ERPNext
[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
erpnext = ">=15.0.0,<16.0.0" # Only if app extends ERPNext
CRITICAL rules for pyproject.toml:
name MUST match the inner directory name exactlydynamic = ["version"] is REQUIRED — flit reads __version__ from __init__.pyfrappe or erpnext in [project] dependencies (they are not on PyPI)[tool.bench.frappe-dependencies]from setuptools import setup, find_packages
setup(
name="my_custom_app",
version="0.0.1",
description="Description of your app",
author="Your Company",
author_email="[email protected]",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=[],
)
app_name = "my_custom_app"
app_title = "My Custom App"
app_publisher = "Your Company"
app_description = "Description"
app_email = "[email protected]"
app_license = "MIT"
required_apps = ["frappe"] # Or ["frappe", "erpnext"] if extending ERPNext
fixtures = [
{"dt": "Custom Field", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My Custom App"]]},
]
My Custom App
Integrations
Settings
Reports
Rules:
My Custom App → my_custom_app/)__init__.py in every module directorymy_custom_app/
├── my_custom_app/ # "My Custom App" module
│ ├── __init__.py
│ └── doctype/
├── integrations/ # "Integrations" module
│ ├── __init__.py
│ └── doctype/
├── settings/ # "Settings" module
│ ├── __init__.py
│ └── doctype/
└── reports/ # "Reports" module
├── __init__.py
└── report/
doctype/my_doctype/
├── __init__.py # Empty (REQUIRED)
├── my_doctype.json # DocType definition (generated by UI)
├── my_doctype.py # Python controller
├── my_doctype.js # Client script
├── test_my_doctype.py # Unit tests
└── my_doctype_dashboard.py # Dashboard config
[pre_model_sync]
# Runs BEFORE schema sync — old fields still available
myapp.patches.v1_0.backup_old_data
[post_model_sync]
# Runs AFTER schema sync — new fields available
myapp.patches.v1_0.populate_new_fields
myapp.patches.v1_0.cleanup_data
# myapp/patches/v1_0/populate_new_fields.py
import frappe
def execute():
"""Populate new fields with default values."""
batch_size = 1000
offset = 0
while True:
records = frappe.get_all(
"MyDocType",
filters={"new_field": ["is", "not set"]},
fields=["name"],
limit_page_length=batch_size,
limit_start=offset,
)
if not records:
break
for record in records:
frappe.db.set_value(
"MyDocType", record.name,
"new_field", "default_value",
update_modified=False,
)
frappe.db.commit()
offset += batch_size
| Situation | Section | Reason |
|-----------|---------|--------|
| Migrate data from old field | [pre_model_sync] | Old field still exists |
| Rename field + preserve data | [pre_model_sync] | Old name still available |
| Populate new required fields | [post_model_sync] | New field already exists |
| General data cleanup | [post_model_sync] | No schema dependency |
# Patches run ONCE. To re-run, make the line unique with a comment:
myapp.patches.v1_0.my_patch #2024-01-15
before_migrate hooks execute[pre_model_sync] patches execute[post_model_sync] patches executeafter_migrate hooks executefixtures = [
"Category", # All records
{"dt": "Custom Field", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Role", "filters": [["name", "like", "MyApp%"]]},
]
# Export fixtures to JSON files
bench --site mysite export-fixtures --app my_custom_app
# Import happens automatically during bench migrate or install-app
| What | Fixtures | Patches |
|------|:--------:|:-------:|
| Custom Fields | YES | NO |
| Property Setters | YES | NO |
| Roles, Workflows | YES | NO |
| Data transformation | NO | YES |
| One-time migration | NO | YES |
| Seed configuration data | YES | NO |
ALWAYS order fixtures so dependencies come first:
fixtures = [
"Workflow State", # FIRST — Workflow depends on states
"Workflow", # SECOND
]
| Aspect | v14 | v15+ | v16+ |
|--------|-----|------|------|
| Build config | setup.py | pyproject.toml | pyproject.toml |
| Build backend | setuptools | flit_core | flit_core |
| Dependencies file | requirements.txt | pyproject.toml | pyproject.toml |
| Python minimum | >=3.10 | >=3.10 | >=3.14 |
| INI patches | YES | YES | YES |
pyproject.toml with flit_core build-systemrequirements.txt to [project] dependencies__version__ in __init__.pysetup.py, MANIFEST.in, requirements.txtbench get-app and bench install-app__version__ in __init__.py — flit build fails without itdynamic = ["version"] in pyproject.tomlmodules.txt__init__.py in EVERY Python directory[tool.bench.frappe-dependencies], NEVER in [project] dependenciesmodule field on Custom Fields and Property Setters for correct fixture exportfrappe or erpnext in pip dependencies (not on PyPI — install fails)frappe.db.commit()modules.txt only)| File | Contents |
|------|----------|
| structure.md | Complete directory structure for v14 and v15 |
| pyproject-toml.md | Full pyproject.toml and setup.py configuration |
| modules.md | Module organization, naming, workspaces |
| patches.md | Patch syntax, pre/post model sync, batch processing |
| fixtures.md | Fixture configuration, filters, common DocTypes |
| examples.md | Complete minimal and ERPNext extension app examples |
| anti-patterns.md | Top 10 mistakes and corrections |
frappe-syntax-hooks — Full hooks.py referencefrappe-syntax-controllers — DocType controller methodsfrappe-impl-customapp — Implementation patterns and workflowsGuide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take impertio-studio/frappe-syntax-customapp from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.