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.

Share this article

Comments

Join the discussion. Got a question, found an issue, or want to share your experience?

Leave a Comment

Your email stays private. We just use it for replies.

Nothing to preview yet.

Use **bold**, *italic*, `code`, ```code blocks```, [link](url), > quote, - list