Customising ERPNext safely: DocTypes, custom fields and server scripts
When to use Customize Form, custom apps, client scripts or server scripts in ERPNext — and how to keep your changes upgrade-safe.
ERPNext gives you at least five ways to change behaviour. Picking the right one decides whether your next bench update is uneventful.
The customisation ladder
- Customize Form — add custom fields, change labels, defaults, hide fields. Stored as Property Setters and Custom Fields; fully upgrade-safe.
- Client Script — JavaScript running in the form, for dynamic UI and validation feedback.
- Server Script — sandboxed Python for document events, scheduled jobs and API endpoints. No filesystem access, no arbitrary imports.
- Custom App — a proper Frappe app with hooks, new DocTypes, tests and version control. The right answer for anything substantial.
- Core edits — never. They are overwritten on update.
Example: validation with a server script
# Server Script, type: DocType Event, DocType: Sales Order, Event: Before Save
if doc.grand_total > 100000 and not doc.custom_approved_by:
frappe.throw("Orders above 100,000 require an approver.")
Example: client script for UX
frappe.ui.form.on('Sales Order', {
customer(frm) {
if (!frm.doc.customer) return;
frappe.db.get_value('Customer', frm.doc.customer, 'custom_credit_hold')
.then(r => {
if (r.message.custom_credit_hold) {
frm.dashboard.set_headline_alert('Customer is on credit hold');
}
});
}
});
Custom app hooks
In a custom app, wire events declaratively in hooks.py so they are versioned with your code:
doc_events = {
"Sales Order": {
"on_submit": "my_app.sales.hooks.notify_warehouse",
"validate": "my_app.sales.hooks.check_credit",
}
}
scheduler_events = {
"daily": ["my_app.tasks.expire_quotations"],
}
Rules that keep upgrades boring
- Prefix every custom field with
custom_— ERPNext v15+ does this automatically and it prevents collisions with future core fields. - Export your customisations (
bench --site x export-fixtures) into a custom app so they travel between environments. - Never modify standard DocTypes in Developer Mode on production.
- Write tests for server-side logic;
bench --site x run-tests --app my_appin CI catches most upgrade breakage. - Read the release notes for renamed fields before upgrading a major version.
Reporting
Use Query Reports (SQL) for read-only analysis and Script Reports for computed columns. Both live in a custom app when they matter, so they are reviewable and deployable rather than clicked into production.