doctype-patterns

Frappe DocType Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "doctype-patterns" with this command: npx skills add unityappsuite/frappe-claude/unityappsuite-frappe-claude-doctype-patterns

Frappe DocType Patterns

Comprehensive guide to creating and configuring DocTypes in Frappe Framework, the core building block for all Frappe applications.

When to Use This Skill

  • Creating new DocTypes

  • Adding or modifying fields on DocTypes

  • Designing data models and relationships

  • Setting up naming patterns and autoname

  • Configuring permissions and workflows

  • Creating child tables

  • Working with Virtual or Single DocTypes

DocType Directory Structure

When you create a DocType named "My Custom DocType" in module "My Module":

my_app/ └── my_module/ └── doctype/ └── my_custom_doctype/ ├── my_custom_doctype.json # DocType definition ├── my_custom_doctype.py # Python controller ├── my_custom_doctype.js # Client script ├── test_my_custom_doctype.py # Test file └── init.py

DocType JSON Structure

{ "name": "My Custom DocType", "module": "My Module", "doctype": "DocType", "engine": "InnoDB", "field_order": ["field1", "field2"], "fields": [ { "fieldname": "field1", "fieldtype": "Data", "label": "Field 1", "reqd": 1 } ], "permissions": [ { "role": "System Manager", "read": 1, "write": 1, "create": 1, "delete": 1 } ], "autoname": "naming_series:", "naming_rule": "By "Naming Series" field", "is_submittable": 0, "istable": 0, "issingle": 0, "track_changes": 1, "sort_field": "modified", "sort_order": "DESC" }

Field Types Reference

Text Fields

Type Description Use Case

Data

Single line text (140 chars) Names, codes, short text

Small Text

Multi-line text Short descriptions

Text

Multi-line text (unlimited) Long descriptions

Text Editor

Rich text with formatting Content, notes

Code

Syntax-highlighted code Python, JS, JSON

HTML Editor

WYSIWYG HTML Email templates

Markdown Editor

Markdown input Documentation

Password

Masked input Secrets (stored encrypted)

Numeric Fields

Type Description Use Case

Int

Integer Counts, quantities

Float

Decimal number Measurements

Currency

Money with precision Prices, amounts

Percent

0-100 percentage Discounts, rates

Rating

Star rating (0-1) Reviews, scores

Date/Time Fields

Type Description Use Case

Date

Date only Birth dates, due dates

Datetime

Date and time Timestamps

Time

Time only Schedules

Duration

Time duration Task duration

Selection Fields

Type Description Use Case

Select

Dropdown options Status, type

Check

Boolean checkbox Flags, toggles

Autocomplete

Text with suggestions Tags

Link Fields

Type Description Use Case

Link

Reference to another DocType Foreign key relationship

Dynamic Link

Reference based on another field Polymorphic links

Table

Child table (1-to-many) Line items, details

Table MultiSelect

Many-to-many via link Multiple selections

Special Fields

Type Description Use Case

Attach

Single file attachment Documents

Attach Image

Image with preview Photos, logos

Image

Display image from URL field Gallery

Signature

Signature pad Approvals

Geolocation

Map coordinates Locations

Barcode

Barcode/QR display Inventory

JSON

JSON data Configuration

Layout Fields

Type Description Use Case

Section Break

Horizontal section divider Form organization

Column Break

Vertical column divider Multi-column layout

Tab Break

Tab navigation Large forms

HTML

Static HTML content Instructions, headers

Heading

Section heading Visual separation

Button

Clickable button Actions

Field Options

Common Field Properties

{ "fieldname": "customer", "fieldtype": "Link", "label": "Customer", "options": "Customer", "reqd": 1, "unique": 0, "in_list_view": 1, "in_standard_filter": 1, "in_global_search": 1, "bold": 1, "read_only": 0, "hidden": 0, "print_hide": 0, "no_copy": 0, "allow_in_quick_entry": 1, "translatable": 0, "default": "", "description": "Select the customer", "depends_on": "eval:doc.is_customer", "mandatory_depends_on": "eval:doc.status=='Active'", "read_only_depends_on": "eval:doc.docstatus==1" }

Link Field Options

{ "fieldname": "customer", "fieldtype": "Link", "options": "Customer", "filters": { "disabled": 0, "customer_type": "Company" }, "ignore_user_permissions": 0 }

Select Field Options

{ "fieldname": "status", "fieldtype": "Select", "options": "\nDraft\nPending\nApproved\nRejected", "default": "Draft" }

Dynamic Link

{ "fieldname": "party_type", "fieldtype": "Link", "options": "DocType" }, { "fieldname": "party", "fieldtype": "Dynamic Link", "options": "party_type" }

Naming Patterns (autoname)

Naming Series

{ "autoname": "naming_series:", "naming_rule": "By "Naming Series" field" }

Add a naming_series field:

{ "fieldname": "naming_series", "fieldtype": "Select", "options": "INV-.YYYY.-\nINV-.MM.-.YYYY.-", "default": "INV-.YYYY.-" }

Field-Based Naming

{ "autoname": "field:customer_code", "naming_rule": "By fieldname" }

Expression-Based

{ "autoname": "format:{customer_type}-{###}", "naming_rule": "Expression" }

Hash/Random

{ "autoname": "hash", "naming_rule": "Random" }

Prompt (Manual)

{ "autoname": "Prompt", "naming_rule": "Set by user" }

Controller Lifecycle Hooks

my_doctype.py

import frappe from frappe.model.document import Document

class MyDocType(Document): # ===== BEFORE DATABASE OPERATIONS =====

def autoname(self):
    """Set the document name before saving"""
    self.name = f"{self.prefix}-{frappe.generate_hash()[:8]}"

def before_naming(self):
    """Called before autoname, can modify naming logic"""
    pass

def validate(self):
    """Validate data before save (called on insert and update)"""
    self.validate_dates()
    self.calculate_totals()

def before_validate(self):
    """Called before validate"""
    pass

def before_save(self):
    """Called before document is saved to database"""
    self.modified_by_script = True

def before_insert(self):
    """Called before new document is inserted"""
    self.set_defaults()

# ===== AFTER DATABASE OPERATIONS =====

def after_insert(self):
    """Called after new document is inserted"""
    self.notify_users()

def on_update(self):
    """Called after document is saved (insert or update)"""
    self.update_related_docs()

def after_save(self):
    """Called after on_update, always runs"""
    pass

def on_change(self):
    """Called when document changes in database"""
    pass

# ===== SUBMISSION WORKFLOW =====

def before_submit(self):
    """Called before document is submitted"""
    self.validate_for_submit()

def on_submit(self):
    """Called after document is submitted"""
    self.create_gl_entries()

def before_cancel(self):
    """Called before document is cancelled"""
    self.validate_cancellation()

def on_cancel(self):
    """Called after document is cancelled"""
    self.reverse_gl_entries()

def on_update_after_submit(self):
    """Called when submitted doc is updated (limited fields)"""
    pass

# ===== DELETION =====

def before_delete(self):
    """Called before document is deleted"""
    self.check_dependencies()

def after_delete(self):
    """Called after document is deleted"""
    self.cleanup_attachments()

def on_trash(self):
    """Called when document is trashed"""
    pass

def after_restore(self):
    """Called after document is restored from trash"""
    pass

# ===== CUSTOM METHODS =====

def validate_dates(self):
    if self.end_date and self.start_date > self.end_date:
        frappe.throw("End date cannot be before start date")

def calculate_totals(self):
    self.total = sum(d.amount for d in self.items)

Child Table (Table Field)

Parent DocType

{ "fieldname": "items", "fieldtype": "Table", "label": "Items", "options": "My DocType Item", "reqd": 1 }

Child DocType JSON

{ "name": "My DocType Item", "module": "My Module", "doctype": "DocType", "istable": 1, "editable_grid": 1, "fields": [ { "fieldname": "item", "fieldtype": "Link", "options": "Item", "in_list_view": 1, "reqd": 1 }, { "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1 }, { "fieldname": "rate", "fieldtype": "Currency", "in_list_view": 1 }, { "fieldname": "amount", "fieldtype": "Currency", "in_list_view": 1, "read_only": 1 } ] }

Single DocType (Settings)

For application settings that have only one record:

{ "name": "My App Settings", "module": "My Module", "doctype": "DocType", "issingle": 1, "fields": [ { "fieldname": "enable_feature", "fieldtype": "Check", "label": "Enable Feature" }, { "fieldname": "api_key", "fieldtype": "Password", "label": "API Key" } ] }

Access in code:

settings = frappe.get_single("My App Settings") if settings.enable_feature: do_something()

Virtual DocType

DocType without database table, computed on-the-fly:

{ "name": "My Virtual DocType", "module": "My Module", "doctype": "DocType", "is_virtual": 1 }

Controller:

class MyVirtualDocType(Document): @staticmethod def get_list(args): # Return list of virtual documents return [{"name": "doc1", "value": 100}]

@staticmethod
def get_count(args):
    return len(MyVirtualDocType.get_list(args))

@staticmethod
def get_stats(args):
    return {}

Permissions

{ "permissions": [ { "role": "System Manager", "read": 1, "write": 1, "create": 1, "delete": 1, "submit": 1, "cancel": 1, "amend": 1, "report": 1, "export": 1, "import": 1, "share": 1, "print": 1, "email": 1 }, { "role": "Sales User", "read": 1, "write": 1, "create": 1, "if_owner": 1 } ] }

Best Practices

Naming Conventions

  • Use singular names: "Customer" not "Customers"

  • Use Title Case with spaces: "Sales Invoice"

  • Fieldnames use snake_case: customer_name

Field Design

  • Put most important fields first

  • Use Section Breaks to organize

  • Use Tab Breaks for complex forms

  • Set in_list_view for key fields

  • Set in_standard_filter for filterable fields

Performance

  • Index frequently queried fields with search_index: 1

  • Use read_only to prevent unnecessary validation

  • Limit child table rows with max_attachments

Data Integrity

  • Use unique: 1 for unique constraints

  • Set appropriate reqd (required) flags

  • Use depends_on for conditional visibility

  • Use mandatory_depends_on for conditional requirements

Common Patterns

Status Field Pattern

{ "fieldname": "status", "fieldtype": "Select", "options": "\nDraft\nPending Approval\nApproved\nRejected", "default": "Draft", "in_list_view": 1, "in_standard_filter": 1, "read_only": 1, "allow_on_submit": 1 }

Amount Calculation Pattern

[ {"fieldname": "qty", "fieldtype": "Float"}, {"fieldname": "rate", "fieldtype": "Currency"}, {"fieldname": "amount", "fieldtype": "Currency", "read_only": 1} ]

With controller:

def validate(self): for item in self.items: item.amount = flt(item.qty) * flt(item.rate) self.total = sum(item.amount for item in self.items)

Linked Document Pattern

{ "fieldname": "customer", "fieldtype": "Link", "options": "Customer", "reqd": 1 }, { "fieldname": "customer_name", "fieldtype": "Data", "fetch_from": "customer.customer_name", "read_only": 1 }

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bench-commands

No summary provided by upstream source.

Repository SourceNeeds Review
General

frappe-api

No summary provided by upstream source.

Repository SourceNeeds Review
General

server-scripts

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

client-scripts

No summary provided by upstream source.

Repository SourceNeeds Review