Doctypes are the building blocks of ERPNext. Every form, every table, every record in ERPNext is a Doctype. If you want to extend ERPNext to fit your business, you need to know how to create custom Doctypes. This guide covers everything from basic creation to advanced configurations.
What Is a Doctype?
A Doctype in ERPNext is essentially a data model. When you create a Doctype, Frappe automatically generates a database table, a REST API, a form view, and a list view. You don’t write any of that code yourself — the framework handles it.
STEP 1 Create a Custom App
Never modify ERPNext core files directly. Always create a custom app for your changes.
cd frappe-bench
bench new-app my_company
bench --site mysite.local install-app my_company
STEP 2 Create a New Doctype
You can create Doctypes through the UI or command line. Let’s use the UI.
Go to the search bar and type “Doctype” then click “New Doctype”. Fill in these fields:
Name: Project Task
Module: My Company
Is Submittable: No (check this if you need approval workflows)
STEP 3 Add Fields
Add the fields your Doctype needs. Common field types:
Data - Short text (names, titles)
Text Editor - Rich text content
Select - Dropdown options
Link - Reference to another Doctype
Date - Date picker
Int - Whole numbers
Currency - Money values
Check - Boolean checkbox
Table - Child table (sub-records)
STEP 4 Add Naming Rules
Every Doctype needs a naming rule to generate unique IDs for each record.
# Auto-increment
PT-.#####
# Result: PT-00001, PT-00002, etc.
# Based on a field
field:task_name
# Using a naming series
naming_series
STEP 5 Create via Command Line (Alternative)
You can also create a Doctype using the bench CLI and then define the fields in the JSON file.
bench new-doctype "Project Task" --module "My Company" --app my_company
This creates a JSON file at:
apps/my_company/my_company/my_company/doctype/project_task/project_task.json
STEP 6 Add Server-Side Logic
Add Python code to handle events like validation, before save, or after submit.
# project_task.py
import frappe
from frappe.model.document import Document
class ProjectTask(Document):
def validate(self):
if not self.task_name:
frappe.throw("Task name is required")
def before_save(self):
self.task_name = self.task_name.strip()
def on_update(self):
frappe.msgprint(f"Task {self.name} has been updated")
STEP 7 Add Client-Side Logic
// project_task.js
frappe.ui.form.on('Project Task', {
refresh: function(frm) {
if (!frm.is_new()) {
frm.add_custom_button('Mark Complete', function() {
frm.set_value('status', 'Completed');
frm.save();
});
}
},
task_name: function(frm) {
// Runs when task_name field changes
console.log('Task name changed to:', frm.doc.task_name);
}
});
STEP 8 Migrate and Test
bench --site mysite.local migrate
bench --site mysite.local clear-cache
bench start
Go to your site and search for “Project Task” in the search bar. You should see your new Doctype with all the fields you defined. Create a few test records to make sure everything works as expected.
Comments
Join the discussion. Got a question, found an issue, or want to share your experience?