Skip to content
frappe-controller logo

Generate DocType Controller

frappe-controller

Generate Frappe-style DocType controllers with lifecycle methods for microservices.

SKILL.md

Full skill instructions

Generate DocType Controller

Create document controller classes with lifecycle methods following Frappe patterns.

When to Use

  • Need business logic for DocType
  • Want lifecycle hooks (validate, before_insert, etc.)
  • Prefer class-based controllers
  • Need reusable methods

Core Patterns

1. Controller Structure

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

2. Register Controller

from frappe_microservice.controller import setup_controllers

app = create_microservice("my-service")
setup_controllers(app, controllers_directory="./​controllers")

3. Lifecycle Methods

Available: before_validate, validate, before_insert, after_insert, before_update, after_update, before_save, after_save, before_delete, on_trash, on_cancel, on_submit

4. Helper Methods

  • self.throw(message) - Raise validation error
  • self.get(field, default=None) - Get field value
  • self.set(field, value) - Set field value
  • self.has_value_changed(fieldname) - Check if changed
  • self.get_value_before_save(fieldname) - Get old value

Key Patterns

  1. Validation: Use validate() for business rules
  2. Defaults: Set in before_insert()
  3. Notifications: Send in after_insert() or after_update()
  4. Calculations: Create reusable methods
  5. Error Handling: Use self.throw() for validation errors

File Naming

  • File: sales_order.py → Class: SalesOrder → DocType: Sales Order

Remember: This skill is model-invoked. Claude will use it autonomously when detecting controller development needs.

Decision Tree & Reference

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.

Hook selection (what do you need?)

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)

Quick reference — class, file, and key methods (Frappe)

ItemConvention
DocType nameTitle Case (e.g. Sales Order)
Class namePascalCase (e.g. SalesOrder)
File path (typical app)module/​doctype/​sales_order/​sales_order.py
Base classfrom frappe.model.document import Document
MethodRole
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(...))

validate vs on_update

Aspectvalidateon_update
WhenBefore DB writeAfter DB write
self.x = y persisted?YesNo — use db_set or frappe.db.set_value
Abort save with throw?YesToo late — document already saved
  • NEVER put blocking validation-only logic in on_update — use validate() (or before_submit / similar as appropriate).

Lifecycle execution order (Frappe)

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

Critical rules — ALWAYS / NEVER

  1. After on_update: direct self.field = value is not persisted — use self.db_set(...) or frappe.db.set_value(...).
  2. NEVER call frappe.db.commit() inside controllers — Frappe commits at end of request; manual commit risks partial updates.
  3. ALWAYS call super().validate() (and equivalents) when overriding hooks so base/​ERPNext logic still runs unless you intentionally replace it.
  4. ALWAYS use self.flags (or equivalent) for data passed between hooks in one transaction — avoid global/​external mutable state for this.
  5. NEVER duplicate “must-block-save” validation in on_update — validate in validate() / before_submit as applicable.
  6. Submittable documents: ALWAYS implement on_submit and on_cancel together — ALWAYS reverse on_submit side effects in on_cancel.

Controller vs Server Script (Frappe apps)

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)

Anti-pattern quick check

Do NOTDo instead
Expect self.x = y in on_update to savedb_set / frappe.db.set_value
self.save() recursively from on_updateRisks loops; use db_set or enqueue work
frappe.db.commit() in controllersLet the framework manage the transaction
Heavy work blocking in validateConsider frappe.enqueue() from on_update
Skip super() in overridesCall parent hooks first unless fully replacing behavior
frappe.get_doc() in hot loopsPrefer frappe.get_cached_doc() when applicable

More skills from vyogotech

frappe-data-migration-generator logo
vyogotech/frappe-apps-manager

frappe-data-migration-generator

Generate data migration scripts for Frappe. Use when migrating data from legacy systems, transforming data structures, or importing large datasets.

11 0
View
frappe-documentation-generator logo
vyogotech/frappe-apps-manager

frappe-documentation-generator

Generate API documentation, user guides, and technical documentation for Frappe apps. Use when documenting APIs, creating user guides, or generating OpenAPI specs.

11 0
View
frappe-containerfile-generator logo
vyogotech/frappe-apps-manager

frappe-containerfile-generator

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.

11 0
View
Generate TDD Tests logo
vyogotech/frappe-apps-manager

Generate TDD Tests

Enforce the Iron Law of TDD for Frappe apps. Red-Green-Refactor cycle for DocTypes and Controllers.

11 0
View
frappe-app-scaffold logo
vyogotech/frappe-apps-manager

frappe-app-scaffold

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.

11 0
View
frappe-api-handler logo
vyogotech/frappe-apps-manager

frappe-api-handler

Generate whitelisted API methods and REST endpoints for standard Frappe and microservices.

11 0
View
Validate Microservice Code logo
vyogotech/frappe-apps-manager

Validate Microservice Code

Validate code follows frappe-microservice-lib patterns, security best practices, and framework conventions.

11 0
View
frappe-tenant-query logo
vyogotech/frappe-apps-manager

frappe-tenant-query

Generate tenant-isolated database queries using TenantAwareDB to prevent cross-tenant access.

11 0
View
frappe-secure-endpoint logo
vyogotech/frappe-apps-manager

frappe-secure-endpoint

Generate secure, tenant-aware API endpoints with authentication and tenant isolation.

11 0
View
frappe-web-form-builder logo
vyogotech/frappe-apps-manager

frappe-web-form-builder

Generate Frappe Web Forms for public-facing forms. Use when creating customer portals, registration forms, surveys, or public data collection forms.

11 0
View
frappe-unit-test-generator logo
vyogotech/frappe-apps-manager

frappe-unit-test-generator

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.

11 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View