frappe-agent-architect
Use when designing multi-app Frappe architectures, deciding whether to split functionality into separate apps, or implementing cross-app communication…
Use when building Document Controllers in a custom Frappe app: file creation, lifecycle hooks, validation, autoname, submittable workflows, controller override, child table controllers, flags system, migration from hooks.py and Server Scripts. Keywords: how to implement
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-impl-controllers --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/frappe-impl-controllersContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when building Document Controllers in a custom Frappe app: file creation, lifecycle hooks, validation, autoname, submittable workflows, controller override, child table controllers, flags system, migration from hooks.py and Server Scripts. Keywords: how to implement
name: frappe-impl-controllers description: > Use when building Document Controllers in a custom Frappe app: file creation, lifecycle hooks, validation, autoname, submittable workflows, controller override, child table controllers, flags system, migration from hooks.py and Server Scripts. Keywords: how to implement controller, which hook to use, validate vs on_update, override controller, submittable document, autoname, flags, extend_doctype_class, controller testing, child table controller, which hook to use, when does validate run, how to override save, document lifecycle. license: MIT compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16." metadata: author: OpenAEC-Foundation version: "2.0"
Step-by-step workflows for building server-side DocType logic with full Python power. For exact syntax, see `frappe-syntax-controllers`.
**Version**: v14/v15/v16 | **v15+**: Supports auto-generated type annotations
NEED full Python (imports, classes, generators)? → Controller NEED external libraries (requests, pandas)? → Controller NEED try/except with rollback? → Controller NEED frappe.enqueue() for background jobs? → Controller NEED to extend standard ERPNext DocType? → Controller Quick validation without custom app? → Server Script Simple auto-fill or notification? → Server Script
**Rule**: ALWAYS use Controllers when you need a custom app. ALWAYS use Server Scripts for no-code prototyping.
**Step 1**: Create DocType via Frappe UI or `bench new-doctype`
**Step 2**: File is auto-generated at:
apps/myapp/myapp/{module}/doctype/{doctype_name}/{doctype_name}.py**Step 3**: Implement the controller class:
import frappe
from frappe import _
from frappe.model.document import Document
class MyDocType(Document):
def validate(self):
self.validate_dates()
self.calculate_totals()
def validate_dates(self):
if self.from_date and self.to_date and self.from_date > self.to_date:
frappe.throw(_("From Date cannot be after To Date"))
def calculate_totals(self):
self.total = sum(item.amount for item in self.items)**Step 4**: Run `bench restart` (or `bench watch` for hot-reload in dev)
**Naming convention**: DocType "Sales Order" → class `SalesOrder`, file `sales_order.py`
WHAT DO YOU WANT?
├── Validate data / calculate fields before save?
│ └── validate — changes to self ARE saved
│
├── Action AFTER save (emails, linked docs, logs)?
│ └── on_update — changes to self NOT saved (use db_set)
│
├── Only for NEW documents?
│ └── after_insert
│
├── Before/after SUBMIT?
│ ├── Check before submit → before_submit
│ └── Ledger entries after → on_submit
│
├── Before/after CANCEL?
│ ├── Prevent cancel → before_cancel
│ └── Reverse entries → on_cancel
│
├── Before DELETE?
│ └── on_trash (throw to prevent)
│
├── Custom document naming?
│ └── autoname
│
└── Detect ANY change (including db_set)?
└── on_change> See [references/decision-tree.md](references/decision-tree.md) for all hooks with execution order.
| Aspect | `validate` | `on_update` | |--------|-----------|-------------| | When | Before DB write | After DB write | | `self.x = y` saved? | YES | **NO** — use `db_set` | | Can abort with throw? | YES | Already saved | | `get_doc_before_save()` | Available | Available | | Use for | Validation, calculations | Notifications, linked docs |
# WRONG — changes in on_update are NOT saved
def on_update(self):
self.status = "Completed" # LOST!
# CORRECT — use db_set
def on_update(self):
frappe.db.set_value(self.doctype, self.name, "status", "Completed")def validate(self):
errors = []
if not self.items:
errors.append(_("At least one item is required"))
for item in self.items:
if item.qty <= 0:
errors.append(_("Row {0}: Qty must be positive").format(item.idx))
if self.from_date > self.to_date:
errors.append(_("From Date cannot be after To Date"))
if errors:
frappe.throw("<br>".join(errors))def validate(self):
old = self.get_doc_before_save()
if old and old.status != self.status:
self.flags.status_changed = True
self.status_changed_on = frappe.utils.now()
def on_update(self):
if self.flags.get('status_changed'):
self.notify_status_change()**Rule**: ALWAYS use `self.flags` to pass data between hooks. NEVER rely on external state.
from frappe.model.naming import getseries
def autoname(self):
# Format: PRJ-CUST-2025-001
code = (self.customer or "GEN")[:4].upper()
year = frappe.utils.getdate(self.start_date or frappe.utils.today()).year
prefix = f"PRJ-{code}-{year}-"
self.name = getseries(prefix, 3)**Alternative — before_naming**:
def before_naming(self):
if self.is_priority:
self.naming_series = "PRIORITY-.#####"
else:
self.naming_series = "STD-.#####"DRAFT (docstatus=0) → submit() → SUBMITTED (docstatus=1) → cancel() → CANCELLED (docstatus=2) submit(): validate → before_submit → [DB: docstatus=1] → on_update → on_submit cancel(): before_cancel → [DB: docstatus=2] → on_cancel
class PurchaseOrder(Document):
def validate(self):
self.validate_items()
self.calculate_totals()
def before_submit(self):
# ONLY submit-specific checks here
if self.total > 100000 and not self.manager_approval:
frappe.thro60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations
Repo: Impertio-Studio/Frappe_Claude_Skill_Package
Use when designing multi-app Frappe architectures, deciding whether to split functionality into separate apps, or implementing cross-app communication…
Use when debugging Frappe errors, using bench console for live inspection, analyzing tracebacks, or reading Frappe log files. Prevents wasted debugging time…
Use when receiving vague or unclear ERPNext/Frappe development requests that need interpretation. Transforms requirements like 'make invoice auto-calculate' or…
Use when migrating a Frappe app between major versions, detecting breaking API changes, or resolving post-migration errors. Prevents failed migrations from…
Use when reviewing or validating Frappe/ERPNext code against best practices and common pitfalls. Checks generated code before deployment, validates against all…
Use when building ERPNext/Frappe API integrations (v14/v15/v16) including REST API, RPC API, authentication, webhooks, and rate limiting. Covers external API…