$npx -y skills add lubusIN/frappe-skills --skill frappe-doctype-developmentCreate and modify Frappe DocTypes including schema design, controllers, child tables, and customization. Use when building data models, adding fields, or implementing document lifecycle logic.
| 1 | # Frappe DocType Development |
| 2 | |
| 3 | Build and modify DocTypes—the core data model abstraction in Frappe Framework. |
| 4 | |
| 5 | ## When to use |
| 6 | |
| 7 | - Creating new DocTypes (standard, single, child table, submittable, tree) |
| 8 | - Adding or modifying fields on existing DocTypes |
| 9 | - Implementing controller logic (validate, before_save, on_submit, etc.) |
| 10 | - Setting up naming series and auto-naming |
| 11 | - Configuring permissions and workflows |
| 12 | - Building child tables and parent-child relationships |
| 13 | |
| 14 | ## Inputs required |
| 15 | |
| 16 | - Target app and module path |
| 17 | - DocType name and type (standard/single/child/submittable/tree) |
| 18 | - Field definitions (name, type, options) |
| 19 | - Permission requirements by role |
| 20 | - Whether workflow is needed |
| 21 | |
| 22 | ## Procedure |
| 23 | |
| 24 | ### 0) Verify environment |
| 25 | |
| 26 | ```bash |
| 27 | # Ensure developer mode is enabled |
| 28 | bench --site <site> console |
| 29 | >>> frappe.conf.developer_mode # Must be True |
| 30 | ``` |
| 31 | |
| 32 | ### 1) Choose DocType type |
| 33 | |
| 34 | | Type | Use Case | Key Setting | |
| 35 | |------|----------|-------------| |
| 36 | | Standard | Multiple records | Default | |
| 37 | | Single | Config/settings (one record) | `issingle: 1` | |
| 38 | | Child Table | Rows in parent table | `istable: 1` | |
| 39 | | Submittable | Draft→Submit→Cancel workflow | `is_submittable: 1` | |
| 40 | | Tree | Hierarchical data | `is_tree: 1` | |
| 41 | | Virtual | External data source | `is_virtual: 1` | |
| 42 | |
| 43 | ### 2) Create DocType |
| 44 | |
| 45 | **Option A: Via UI (recommended for new DocTypes)** |
| 46 | 1. Navigate to DocType List → New |
| 47 | 2. Define fields, permissions, settings |
| 48 | 3. Save (exports to app in developer mode) |
| 49 | |
| 50 | **Option B: Via code** |
| 51 | ```python |
| 52 | # Create DocType JSON in: |
| 53 | # <app>/<module>/doctype/<doctype_name>/<doctype_name>.json |
| 54 | ``` |
| 55 | |
| 56 | ### 3) Define fields |
| 57 | |
| 58 | Common field patterns: |
| 59 | ```json |
| 60 | { |
| 61 | "fieldname": "customer", |
| 62 | "fieldtype": "Link", |
| 63 | "label": "Customer", |
| 64 | "options": "Customer", |
| 65 | "reqd": 1 |
| 66 | } |
| 67 | ``` |
| 68 | |
| 69 | See [references/field-types.md](references/field-types.md) for all field types. |
| 70 | |
| 71 | ### 4) Implement controller |
| 72 | |
| 73 | Create `<doctype_name>.py` alongside the JSON: |
| 74 | |
| 75 | ```python |
| 76 | import frappe |
| 77 | from frappe.model.document import Document |
| 78 | |
| 79 | class MyDocType(Document): |
| 80 | def validate(self): |
| 81 | # Lightweight validation |
| 82 | if not self.customer: |
| 83 | frappe.throw("Customer is required") |
| 84 | |
| 85 | def before_save(self): |
| 86 | # Pre-save normalization |
| 87 | self.full_name = f"{self.first_name} {self.last_name}" |
| 88 | |
| 89 | def after_insert(self): |
| 90 | # Post-create side effects |
| 91 | frappe.publish_realtime("new_doc", {"name": self.name}) |
| 92 | ``` |
| 93 | |
| 94 | ### 5) Set up naming |
| 95 | |
| 96 | ```json |
| 97 | { |
| 98 | "autoname": "naming_series:", |
| 99 | "fields": [ |
| 100 | { |
| 101 | "fieldname": "naming_series", |
| 102 | "fieldtype": "Select", |
| 103 | "options": "PRJ-.YYYY.-\nPRJ-.YYYY.-.###" |
| 104 | } |
| 105 | ] |
| 106 | } |
| 107 | ``` |
| 108 | |
| 109 | Options: `field:fieldname`, `naming_series:`, `hash`, `format:PREFIX-{####}` |
| 110 | |
| 111 | ### 6) Configure permissions |
| 112 | |
| 113 | Set in DocType → Permissions tab: |
| 114 | - Role + Read/Write/Create/Delete/Submit/Cancel |
| 115 | - User permissions for row-level filtering |
| 116 | |
| 117 | ### 7) Add workflow (if needed) |
| 118 | |
| 119 | Create Workflow DocType linking to your DocType with states and transitions. |
| 120 | |
| 121 | ## Verification |
| 122 | |
| 123 | - [ ] DocType appears in list and can create new records |
| 124 | - [ ] All fields save correctly |
| 125 | - [ ] Controller hooks fire (check logs) |
| 126 | - [ ] Permissions enforced for each role |
| 127 | - [ ] Naming series generates correctly |
| 128 | - [ ] Run: `bench --site <site> migrate` succeeds |
| 129 | |
| 130 | ## Failure modes / debugging |
| 131 | |
| 132 | - **DocType not found**: Check module path and app installation |
| 133 | - **Controller not loading**: Verify class name matches DocType name (PascalCase) |
| 134 | - **Fields not saving**: Check fieldtype and options compatibility |
| 135 | - **Permission denied**: Verify role permissions and User Permissions |
| 136 | |
| 137 | ## Escalation |
| 138 | |
| 139 | - For complex permission logic, see [references/permissions.md](references/permissions.md) |
| 140 | - For child table patterns, see [references/child-tables.md](references/child-tables.md) |
| 141 | - For Virtual DocTypes, see [references/virtual-doctypes.md](references/virtual-doctypes.md) |
| 142 | |
| 143 | ## References |
| 144 | |
| 145 | - [references/field-types.md](references/field-types.md) - All field types and options |
| 146 | - [references/controllers.md](references/controllers.md) - Controller lifecycle hooks |
| 147 | - [references/child-tables.md](references/child-tables.md) - Parent-child patterns |
| 148 | - [references/naming.md](references/naming.md) - Naming patterns |
| 149 | |
| 150 | ## Guardrails |
| 151 | |
| 152 | - **Check developer_mode before schema changes**: DocType modifications only export to files when `developer_mode = 1` in site config |
| 153 | - **Verify naming series uniqueness**: Ensure naming series prefixes don't conflict with existing DocTypes |
| 154 | - **Test child tables separately**: Child tables have their own lifecycle; test them in isolation before parent integration |
| 155 | - **Always run migrate after changes**: Schema changes require `bench --site <site> migrate` to apply |
| 156 | - **Validate fieldname conventions**: Use snake_case, max 140 chars, no reserved SQL |