Frappe_Claude_Skill_Package frappe-syntax-doctypes
git clone https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package
T=$(mktemp -d) && git clone --depth=1 https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/source/syntax/frappe-syntax-doctypes" ~/.claude/skills/openaec-foundation-frappe-claude-skill-package-frappe-syntax-doctypes && rm -rf "$T"
skills/source/syntax/frappe-syntax-doctypes/SKILL.mdDocType JSON Design
DocTypes are the foundation of every Frappe application. A DocType defines both the data model (database schema) and the view (form layout). ALWAYS design DocTypes before writing any controller logic.
Quick Reference
DocType JSON Top-Level Properties
| Property | Type | Purpose |
|---|---|---|
| str | DocType identifier (singular, e.g. "Sales Invoice") |
| str | App module this DocType belongs to |
| bool | Enables Draft -> Submitted -> Cancelled workflow |
| bool | Enables NestedSet hierarchy (lft/rgt columns) |
| bool | No database table; data from custom backend |
| bool | Single-instance settings document |
| bool | Child table DocType (embedded in parent) |
| bool | Enables calendar/gantt views |
| bool | Stores version history on every save |
| bool | Tracks which users viewed the document |
| bool | Counts total document views |
| bool | Permits renaming after creation |
| bool | Enables "Duplicate" action |
| bool | Enables Data Import for this DocType |
| str | Naming method selector (see Naming section) |
| str | Naming pattern string |
| str | Field used as display title |
| str | Comma-separated fields for search results |
| bool | Display title instead of name in Link fields |
| str | Field containing image for avatar display |
| str | Default sort column |
| str | "ASC" or "DESC" |
| str | Print Format name |
| int | Attachment limit |
Common Fieldtypes (Quick Lookup)
| Fieldtype | Stores | DB Column |
|---|---|---|
| Data | Text up to 140 chars | VARCHAR(140) |
| Link | Reference to another DocType | VARCHAR(140) |
| Dynamic Link | Reference to any DocType | VARCHAR(140) |
| Select | Single choice from options | VARCHAR(140) |
| Table | Child table rows | Separate table |
| Table MultiSelect | Multi-select link rows | Separate table |
| Check | Boolean 0/1 | TINYINT |
| Int | Whole number | INT |
| Float | Decimal (9 places) | DECIMAL |
| Currency | Money value (6 decimals) | DECIMAL |
| Date | Calendar date | DATE |
| Datetime | Date + time | DATETIME |
| Text Editor | Rich text (HTML) | LONGTEXT |
| Attach | File reference | VARCHAR(140) |
| Small Text | Short multi-line text | TEXT |
| Long Text | Unlimited text | LONGTEXT |
Full fieldtype reference with all 35+ types: references/fieldtypes.md
Essential Field Properties
| Property | Type | Purpose |
|---|---|---|
| bool | Field is mandatory |
| bool | Database UNIQUE constraint |
| bool | Database INDEX for faster queries |
| bool | Show in list view columns |
| bool | Show as filter in list view |
| bool | Show in document preview |
| bool | Editable after submission |
| bool | Not editable by user |
| bool | Not visible on form |
| str | Visibility condition (e.g. ) |
| str | Conditional mandatory |
| str | Conditional read-only |
| str | Auto-populate from linked doc (e.g. ) |
| bool | Only fetch when field is empty |
| str | Fieldtype-specific (DocType name, select options, etc.) |
| str | Default value (supports , , etc.) |
| str | Help text below field |
| bool | Section starts collapsed (Section Break only) |
Decision Tree: Which DocType Type?
Need to store data? ├─ YES: Need multiple records? │ ├─ YES: Need submit/cancel workflow? │ │ ├─ YES → Standard DocType + is_submittable=1 │ │ └─ NO: Need hierarchy/tree? │ │ ├─ YES → Tree DocType (is_tree=1) │ │ └─ NO: Embedded in parent? │ │ ├─ YES → Child DocType (istable=1) │ │ └─ NO → Standard DocType │ └─ NO: Single config/settings → Single DocType (issingle=1) └─ NO: Data from external source → Virtual DocType (is_virtual=1)
Naming Rules
ALWAYS set
naming_rule on the DocType. The autoname field holds the pattern.
| naming_rule Value | autoname Pattern | Example Output |
|---|---|---|
| Set by User | (empty) | User types name manually |
| Autoincrement | (empty) | , , |
| By Fieldname | | Value of that field |
| By Naming Series | | (from series field) |
| Expression | | , |
| Expression (Old Style) | | |
| Random | | Random 10-char string |
| UUID | (empty) | |
| By Script | (custom) | Controller decides |
NEVER use Autoincrement in production -- gaps appear when records are deleted. Use Expression or Naming Series instead.
Full naming reference: references/naming.md
Child Table Design
A Child DocType is a DocType with
istable=1. It ALWAYS belongs to a parent.
Parent side -- add a field with:
:fieldtype
(orTable
)Table MultiSelect
: Child DocType nameoptions
Child records automatically get:
-- name of the parent documentparent
-- DocType of the parentparenttype
-- fieldname of the Table field in parentparentfield
-- row order (1-based)idx
# Adding child rows programmatically doc = frappe.get_doc("Sales Invoice", "INV-001") doc.append("items", { "item_code": "ITEM-001", "qty": 5, "rate": 100.0 }) doc.save()
NEVER create a Child DocType without
. NEVER reference a non-child DocType in a Table field.istable=1
Table vs Table MultiSelect
| Aspect | Table | Table MultiSelect |
|---|---|---|
| UI | Full editable grid with "Add Row" | Tag-style picker, no "Add Row" |
| Child DocType | Full child with many fields | Typically 1 Link field only |
| Use case | Line items, detail rows | Multi-select references |
Single DocType (Settings Pattern)
Set
issingle=1. Data is stored in tabSingles as key-value pairs, NOT in a dedicated table.
# Access Single DocType settings = frappe.get_single("My Settings") value = settings.some_field # Or directly value = frappe.db.get_single_value("My Settings", "some_field")
- NEVER expect a list view for Single DocTypes -- they have exactly one instance.
- ALWAYS use for app-wide configuration (API keys, default values, feature toggles).
Tree DocType (NestedSet)
Set
is_tree=1. Frappe adds lft, rgt, parent_{doctype_fieldname}, old_parent columns automatically.
- ALWAYS define a
in the DocType JSON (e.g.parent_field
for Chart of Accounts).parent_account - The NestedSet model uses
/lft
integers for efficient subtree queries.rgt - NEVER manually edit
/lft
values. Usergt
if corrupted.frappe.utils.nestedset.rebuild_tree()
# Get all descendants descendants = frappe.get_all("Account", filters={"lft": [">", node.lft], "rgt": ["<", node.rgt]}) # Get ancestors (path to root) ancestors = frappe.get_all("Account", filters={"lft": ["<", node.lft], "rgt": [">", node.rgt]}, order_by="lft asc")
Virtual DocType
Set
is_virtual=1. No database table is created. ALWAYS implement these controller methods:
class MyVirtualDoc(Document): def db_insert(self, *args, **kwargs): # Persist to your custom backend pass def load_from_db(self): # Load document data from your source pass def db_update(self, *args, **kwargs): # Update in your custom backend pass def delete(self): # Remove from your custom backend pass @staticmethod def get_list(args): # Return list of documents pass @staticmethod def get_count(args): # Return total count pass @staticmethod def get_stats(args): # Return statistics pass
NEVER use
calls for Virtual DocType data -- they only work with the site database, not your custom backend.frappe.db.*
Customization APIs
Custom Fields (Programmatic)
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields # Dict format: {DocType: [field_dicts]} create_custom_fields({ "Sales Invoice": [ dict(fieldname="custom_tracking", label="Tracking ID", fieldtype="Data", insert_after="naming_series") ], "Purchase Order": [ dict(fieldname="custom_vendor_ref", label="Vendor Ref", fieldtype="Data", insert_after="supplier") ] }, update=True)
Property Setter (Programmatic)
from frappe.custom.doctype.property_setter.property_setter import make_property_setter # Change a field property on an existing DocType make_property_setter("Sales Invoice", "customer", "reqd", 1, "Check") make_property_setter("Sales Invoice", "posting_date", "default", "Today", "Text")
Full customization reference: references/customization.md
Data Masking (v16+)
Fields with
mask=1 hide sensitive values from users without mask permission at the field's permlevel. The server replaces values with patterns like XXXXXXXX before sending to the client. Administrator ALWAYS sees unmasked values.
{ "fieldname": "phone", "fieldtype": "Data", "options": "Phone", "mask": 1, "permlevel": 1 }
Full masking reference: references/data-masking.md
Python Type Stubs
Frappe auto-generates type annotations in controller files via
TypeExporter. Fields get DF.* types inside a TYPE_CHECKING guard:
if TYPE_CHECKING: from frappe.types import DF customer: DF.Link items: DF.Table[SalesInvoiceItem] status: DF.Literal["Draft", "Submitted", "Paid"]
NEVER modify code between
# begin: auto-generated types and # end: auto-generated types.
Full type stubs reference: references/type-stubs.md
Critical Rules
- ALWAYS name DocTypes in singular form ("Sales Invoice", not "Sales Invoices").
- ALWAYS use the
prefix mentally -- the DB table istab
.tabSales Invoice - NEVER exceed 140 characters for Data/Link/Select field values.
- ALWAYS set
on fields used in frequent filters orsearch_index=1
calls.get_list - ALWAYS set
on fields users frequently filter by.in_standard_filter=1 - NEVER use
on child table fields that affect calculations without recalculating totals.allow_on_submit=1 - ALWAYS set
alongsidefetch_if_empty=1
unless you want to overwrite user edits.fetch_from - NEVER define
with raw Python -- usedepends_on
syntax.eval:doc.fieldname == "value"
See Also
- references/fieldtypes.md -- Complete fieldtype reference
- references/naming.md -- All naming methods with examples
- references/examples.md -- Real DocType JSON examples
- references/anti-patterns.md -- Common schema design mistakes
- references/customization.md -- Custom Fields and Property Setter APIs
- references/data-masking.md -- Field-level data masking for privacy (v16+)
- references/type-stubs.md -- Python type hints, DF types, TypeExporter