Skip to content
AI & Agents
Skill

/frappe-impl-customapp

Use when building a custom Frappe app from scratch. Covers bench new-app walkthrough, app structure decisions, adding DocTypes, hooks, patches, fixtures management, development workflow (bench migrate, build, clear-cache), testing, packaging, installing on another site, version

From plugin
frappe-claude-skill-package
17961 skills
Install
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-impl-customapp --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frappe-impl-customapp

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when building a custom Frappe app from scratch. Covers bench new-app walkthrough, app structure decisions, adding DocTypes, hooks, patches, fixtures management, development workflow (bench migrate, build, clear-cache), testing, packaging, installing on another site, version

SKILL.md

frappe-impl-customapp.SKILL.md
name: frappe-impl-customapp
description: >
  Use when building a custom Frappe app from scratch. Covers bench new-app
  walkthrough, app structure decisions, adding DocTypes, hooks, patches,
  fixtures management, development workflow (bench migrate, build,
  clear-cache), testing, packaging, installing on another site, version
  management, and app dependencies for v14/v15/v16. Keywords: create custom
  app, new frappe app, bench new-app, app structure, module creation,
  doctype creation, fixtures, patches, deployment, packaging,
  data migration, patch file, patches.txt, migrate data between DocTypes, create new app from scratch.
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
  author: OpenAEC-Foundation
  version: "2.0"

Frappe Custom App - Implementation

Workflow for building a custom Frappe app from scratch. For exact syntax, see `frappe-syntax-customapp`.

**Version**: v14/v15/v16 compatible

---

Main Decision: Do You Need a Custom App?

WHAT CHANGES DO YOU NEED?
|
+-- Add fields to existing DocType?
|   +-- NO APP NEEDED: Custom Field + Property Setter
|
+-- Simple automation/validation (<50 lines)?
|   +-- NO APP NEEDED: Server Script or Client Script
|
+-- Complex business logic, new DocTypes, or Python code?
|   +-- YES: Create custom app
|
+-- Integration with external system (needs imports)?
|   +-- YES: Custom app REQUIRED (Server Scripts block imports)
|
+-- Custom reports with complex queries?
|   +-- Script Report (no app) vs Query Report (app optional)

**Rule**: ALWAYS start with the simplest solution. Server Scripts + Custom Fields solve 70% of needs without a custom app.

---

Step 1: Create App Structure

cd ~/frappe-bench
bench new-app my_app
# Prompts: Title, Description, Publisher, Email, License

ALWAYS verify immediately:

# my_app/my_app/__init__.py MUST have:
__version__ = "0.0.1"

---

Step 2: Configure pyproject.toml (v15+)

[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "my_app"
authors = [{ name = "Your Company", email = "dev@example.com" }]
description = "Your app description"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
dependencies = [
    "requests>=2.28.0"   # Only PyPI packages here
]

[tool.bench.frappe-dependencies]
frappe = ">=15.0.0,<16.0.0"
# erpnext = ">=15.0.0,<16.0.0"  # Only if needed

**Rule**: NEVER put frappe or erpnext in `[project].dependencies` -- they are NOT on PyPI.

---

Step 3: Configure hooks.py

app_name = "my_app"
app_title = "My App"
app_publisher = "Your Company"
app_description = "Description"
app_email = "dev@example.com"
app_license = "MIT"

required_apps = ["frappe"]  # Or ["frappe", "erpnext"]

fixtures = []  # Configured later

**Rule**: ALWAYS declare `required_apps` with all dependencies.

---

Step 4: Define Modules

# my_app/my_app/modules.txt
My App

| App Size | Module Strategy | |----------|----------------| | 1-5 DocTypes | ONE module with app name | | 6-15 DocTypes | 2-4 modules by functional area | | 15+ DocTypes | Modules by business domain |

**Rule**: Each DocType belongs to EXACTLY one module. Module name in `modules.txt` maps to directory: `My Custom App` --> `my_custom_app/`.

Adding a Module

mkdir -p my_app/my_app/new_module/doctype
touch my_app/my_app/new_module/__init__.py
# Add "New Module" to modules.txt
bench --site mysite migrate

---

Step 5: Install and Create DocTypes

# Install app on site
bench --site mysite install-app my_app

# Create DocType (via UI recommended, or CLI)
bench --site mysite new-doctype "My Document" --module "My App"

This creates:

my_app/my_app/doctype/my_document/
+-- my_document.json    # DocType definition
+-- my_document.py      # Controller
+-- my_document.js      # Client script
+-- test_my_document.py # Tests

---

Step 6: Add Hooks

doc_events (v14/v15/v16)

doc_events = {
    "Sales Invoice": {
        "validate": "my_app.events.sales_invoice.validate",
        "on_submit": "my_app.events.sales_invoice.on_submit"
    }
}

extend_doctype_class (v16 ONLY -- preferred)

extend_doctype_class = {
    "Sales Invoice": "my_app.overrides.sales_invoice.CustomSalesInvoice"
}

**Rule**: ALWAYS call `super().method()` when overriding lifecycle methods in v16.

Scheduler Events

scheduler_events = {
    "daily": ["my_app.tasks.daily_cleanup"],
    "cron": {"0 9 * * 1-5": ["my_app.tasks.morning_report"]}
}

See `frappe-impl-hooks` and `frappe-impl-scheduler` for complete patterns.

---

Step 7: Add Patches

Create Patch File

mkdir -p my_app/my_app/patches/v1_0
touch my_app/my_app/patches/__init__.py
touch my_app/my_app/patches/v1_0/__init__.py
# my_app/my_app/patches/v1_0/populate_defaults.py
import frappe

def execute():
    if not frappe.db.has_column("My DocType", "target_field"):
        return  # Skip if not applicable

    batch_size = 1000
    offset = 0
    while True:
        records = frappe.get_all("My DocType",
            limit_page_length=batch_size, limit_start=offset)
        if not records:
            break
        for r in records:
            frappe.db.set_value("My DocType", r.name,
                "target_field", "default", update_modified=False)
        frappe.db.commit()
        offset += batch_size

Register in patches.txt

[pre_model_sync]
# Patches that run BEFORE schema changes (backup data from deleted fields)

[post_model_sync]
# Patches that run AFTER schema changes (populate new fields)
my_app.patches.v1_0.populate_defaults

**Rules**:

  • ALWAYS check if patch is needed (guard clause)
  • ALWAYS batch process 1000+ records
  • ALWAYS commit after each batch
  • NEVER run untested patches on production

---

Step 8: Fixtures Management

Configure in hooks.py

fixtures =
Read more
Ships withfrappe-claude-skill-package

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Get the whole plugin
Stats
178
Stars
53
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
8mo ago
Created
15d ago
Added

Repo: Impertio-Studio/Frappe_Claude_Skill_Package