frappe-data-migration-generator
Generate data migration scripts for Frappe. Use when migrating data from legacy systems, transforming data structures, or importing large datasets.
frappe-controller
Generate Frappe-style DocType controllers with lifecycle methods for microservices.
Full skill instructions
Create document controller classes with lifecycle methods following Frappe patterns.
from frappe_microservice.controller import DocumentController
import frappe
class SalesOrder(DocumentController):
def validate(self):
if not self.customer:
self.throw("Customer is required")
self.calculate_total()
def before_insert(self):
if not self.status:
self.status = 'Draft'
if not self.transaction_date:
self.transaction_date = frappe.utils.today()
def after_insert(self):
self.send_order_notification()
def calculate_total(self):
self.grand_total = sum(item.amount for item in self.items) if self.items else 0
from frappe_microservice.controller import setup_controllers
app = create_microservice("my-service")
setup_controllers(app, controllers_directory="./controllers")
Available: before_validate, validate, before_insert, after_insert, before_update, after_update, before_save, after_save, before_delete, on_trash, on_cancel, on_submit
self.throw(message) - Raise validation errorself.get(field, default=None) - Get field valueself.set(field, value) - Set field valueself.has_value_changed(fieldname) - Check if changedself.get_value_before_save(fieldname) - Get old valuevalidate() for business rulesbefore_insert()after_insert() or after_update()self.throw() for validation errorssales_order.py → Class: SalesOrder → DocType: Sales OrderRemember: This skill is model-invoked. Claude will use it autonomously when detecting controller development needs.
The following material is from Frappe / ERPNext controller skills (frappe-syntax-controllers, frappe-impl-controllers). Use it when mapping lifecycle behavior or aligning with upstream Frappe patterns. Hook names in this microservice controller may differ slightly from core Frappe (e.g. on_update vs after_update); treat the semantics the same unless your SDK docs say otherwise.
What do you need to do?
|
+-- Validate data or calculate fields?
| +-- validate (changes to self ARE saved)
|
+-- Action AFTER save (emails, sync, linked docs)?
| +-- on_update (changes to self are NOT saved — use db_set / set_value)
|
+-- Only for NEW documents?
| +-- after_insert (runs once on first save only)
|
+-- Custom document name?
| +-- autoname (set self.name)
|
+-- Before/after SUBMIT?
| +-- Validate before submit? -> before_submit
| +-- Create entries after submit? -> on_submit
|
+-- Before/after CANCEL?
| +-- Check linked docs? -> before_cancel
| +-- Reverse entries? -> on_cancel
|
+-- Cleanup before delete?
| +-- on_trash
|
+-- React to ANY value change (including db_set)?
| +-- on_change (MUST be idempotent)
| Item | Convention |
|---|---|
| DocType name | Title Case (e.g. Sales Order) |
| Class name | PascalCase (e.g. SalesOrder) |
| File path (typical app) | module/doctype/sales_order/sales_order.py |
| Base class | from frappe.model.document import Document |
| Method | Role |
|---|---|
autoname() | Custom naming — set self.name |
validate() | Main validation — runs on every save; field changes on self persist |
on_update() | After DB write — assignments to self do not persist without db_set |
on_submit() / on_cancel() | Submittable workflow — implement as a matched pair |
@frappe.whitelist() | Expose method to Desk client (frm.call(...)) |
| Aspect | validate | on_update |
|---|---|---|
| When | Before DB write | After DB write |
self.x = y persisted? | Yes | No — use db_set or frappe.db.set_value |
| Abort save with throw? | Yes | Too late — document already saved |
on_update — use validate() (or before_submit / similar as appropriate).INSERT (new document)
before_insert -> before_naming -> autoname -> before_validate -> validate
-> before_save -> [db_insert] -> after_insert -> on_update -> on_change
SAVE (existing document)
before_validate -> validate -> before_save -> [db_update]
-> on_update -> on_change
SUBMIT (docstatus 0 -> 1)
before_validate -> validate -> before_submit -> [db_update]
-> on_submit -> on_update -> on_change
CANCEL (docstatus 1 -> 2)
before_cancel -> [db_update] -> on_cancel -> on_change
UPDATE AFTER SUBMIT
before_update_after_submit -> [db_update]
-> on_update_after_submit -> on_change
DELETE
on_trash -> [db_delete] -> after_delete
DISCARD [v15+]
before_discard -> [db_set docstatus=2] -> on_discard
on_update: direct self.field = value is not persisted — use self.db_set(...) or frappe.db.set_value(...).frappe.db.commit() inside controllers — Frappe commits at end of request; manual commit risks partial updates.super().validate() (and equivalents) when overriding hooks so base/ERPNext logic still runs unless you intentionally replace it.self.flags (or equivalent) for data passed between hooks in one transaction — avoid global/external mutable state for this.on_update — validate in validate() / before_submit as applicable.on_submit and on_cancel together — ALWAYS reverse on_submit side effects in on_cancel.NEED full Python (imports, classes, libs)? -> Controller
NEED ERPNext/custom app extension / background jobs? -> Controller
Quick validation without a custom app? -> Server Script (where enabled)
| Do NOT | Do instead |
|---|---|
Expect self.x = y in on_update to save | db_set / frappe.db.set_value |
self.save() recursively from on_update | Risks loops; use db_set or enqueue work |
frappe.db.commit() in controllers | Let the framework manage the transaction |
Heavy work blocking in validate | Consider frappe.enqueue() from on_update |
Skip super() in overrides | Call parent hooks first unless fully replacing behavior |
frappe.get_doc() in hot loops | Prefer frappe.get_cached_doc() when applicable |
Generate data migration scripts for Frappe. Use when migrating data from legacy systems, transforming data structures, or importing large datasets.
Generate API documentation, user guides, and technical documentation for Frappe apps. Use when documenting APIs, creating user guides, or generating OpenAPI specs.
Generate Containerfile for Frappe apps using the official frappe/erpnext images with Gunicorn. The recommended pattern copies app source into the bench layout and runs Gunicorn directly.
Enforce the Iron Law of TDD for Frappe apps. Red-Green-Refactor cycle for DocTypes and Controllers.
Canonical folder structure produced by `bench new-app` for modern Frappe (v14/v15+). Use this as the ground truth for any Frappe app file tree — includes pyproject.toml and module management.
Generate whitelisted API methods and REST endpoints for standard Frappe and microservices.
Validate code follows frappe-microservice-lib patterns, security best practices, and framework conventions.
Generate tenant-isolated database queries using TenantAwareDB to prevent cross-tenant access.
Generate secure, tenant-aware API endpoints with authentication and tenant isolation.
Generate Frappe Web Forms for public-facing forms. Use when creating customer portals, registration forms, surveys, or public data collection forms.
Generate comprehensive unit tests for Frappe DocTypes, controllers, and API methods. Use when creating test files, writing test cases, or setting up test infrastructure for Frappe/ERPNext applications.
Generate integration tests for multi-DocType workflows and distributed services in Frappe.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience