Skill125 estrellas del repoactualizado 2mo ago
frappe-syntax-customapp
>
Instalar en Claude Code
Copiargit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-syntax-customapp && cp -r /tmp/frappe-syntax-customapp/skills/source/syntax/frappe-syntax-customapp ~/.claude/skills/frappe-syntax-customappDespués abre una sesión nueva de Claude Code; el skill carga automáticamente.
Definición
SKILL.md
# Frappe Custom App Syntax
Deterministic syntax reference for building Frappe custom apps — scaffolding, configuration, modules, patches, and fixtures.
## Decision Tree
```
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
```
## Creating an App
```bash
# 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
```
## App Directory Structure
### [v15+] pyproject.toml (Primary)
```
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/
```
### [v14] setup.py (Legacy)
```
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/
```
## Critical Files
### __init__.py (REQUIRED)
```python
# my_custom_app/__init__.py
__version__ = "0.0.1"
```
**CRITICAL**: Without `__version__`, the flit build FAILS and the app CANNOT be installed.
### pyproject.toml [v15+]
```toml
[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "my_custom_app"
authors = [
{ name = "Your Company", email = "dev@example.com" }
]
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 exactly
- `dynamic = ["version"]` is REQUIRED — flit reads `__version__` from `__init__.py`
- NEVER put `frappe` or `erpnext` in `[project] dependencies` (they are not on PyPI)
- ALWAYS put Frappe app dependencies in `[tool.bench.frappe-dependencies]`
### setup.py [v14] (Legacy)
```python
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="dev@example.com",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=[],
)
```
### hooks.py (Minimal Skeleton)
```python
app_name = "my_custom_app"
app_title = "My Custom App"
app_publisher = "Your Company"
app_description = "Description"
app_email = "dev@example.com"
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"]]},
]
```
## Modules
### modules.txt
```
My Custom App
Integrations
Settings
Reports
```
**Rules**:
- One module name per line — NEVER leave empty lines or trailing spaces
- Module name uses spaces; directory name uses underscores (`My Custom App` → `my_custom_app/`)
- Every DocType MUST belong to a registered module
- ALWAYS include `__init__.py` in every module directory
### Module Directory Structure
```
my_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 Directory (within a module)
```
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
```
## Patches (Migration Scripts)
### patches.txt with INI Sections
```ini
[pre_model_sync]
# Runs BEFORE schema sync — old fields still available
myapp.patches.v1_0.backup_old_data
[post_model_syn