Say finance wants a delivery window on every Sales Order. ERPNext doesn’t have that field, and you can’t add it to sales_order.json inside the erpnext app without making a core edit that the next update throws away. A Custom Field is the supported way to add it.
What Frappe does when you add one
You can create one from Customize Form on the DocType, or from the Custom Field list directly. Either way you pick a label, a field type and usually an Insert After field to place it.
If you leave the fieldname blank, Frappe builds it from the label and adds a custom_ prefix, so “Delivery Window” becomes custom_delivery_window. The prefix exists to stop your field colliding with a standard field a later release might add. The record itself is named Sales Order-custom_delivery_window, and saving it adds the column to the tabSales Order table.
Changing something about a standard field, like making it mandatory or renaming its label, is a different record type. That’s a Property Setter, and it’s created for you when you edit a standard field in Customize Form.
Custom Field or a field in the DocType JSON
The rule is about ownership. If your own app defines the DocType, add the field to that DocType’s JSON in developer mode, the same way you created it. Our walkthrough on creating a custom DocType in ERPNext shows that route. If the DocType belongs to ERPNext or another app, use a Custom Field. Never edit another app’s JSON to get the column you want.
Shipping it from code
The usual way to get a Custom Field onto other sites is to set its Module and export it with fixtures. That’s what erpfly does, so the field shows up as a JSON file in the pull request instead of a click someone has to repeat on production.
Some apps create fields in Python instead, from an after_install hook or a patch:
# acme_custom/patches/add_delivery_window.py
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
create_custom_fields(
{
"Sales Order": [
{
"fieldname": "custom_delivery_window",
"label": "Delivery Window",
"fieldtype": "Select",
"options": "\nMorning\nAfternoon\nEvening",
"insert_after": "delivery_date",
}
]
}
)
create_custom_fields updates a field that already exists rather than failing, so running it twice is safe. It’s handy when field definitions depend on logic, like a region or a setting.
Mistakes worth avoiding
- Dropping the prefix. Setting a fieldname by hand skips the automatic
custom_prefix. A bare name likedelivery_windowis exactly what a future standard field might be called. - Changing the type later. Once a Custom Field exists, Frappe only allows type changes within close groups, such as Data to Select or Currency to Float. Data to Link isn’t one of them, so decide whether the value should point at another record before users start typing into it.
- Creating it twice. A field typed by hand on staging and again on production often ends up with two slightly different fieldnames, and every report and script then has to guess which one is real.
- Adding too many. Every field on a busy form is something people have to scroll past. The ERPNext customization page has our view on when to say no.