Frappe Form API Guide (v15)
What is the Frappe Form API?
The Frappe Form API provides a client-side programming interface to interact with documents displayed on a form in Desk. It allows developers to respond to form events, change values, refresh fields, call server methods, and manage UI behavior for DocTypes in ERPNext using JavaScript.
The Form API runs in the browser and is typically used inside:
- *.js DocType client scripts
- Custom Script records
- App-level form controllers
- Override scripts
When should you use the Form API?
Use the Form API when:
- You need to handle form lifecycle events (onload, refresh, etc.)
- You want to manipulate field values dynamically
- You are adding validation logic on client-side
- You modify child table rows
- You trigger server logic from UI
- You hide/show fields dynamically
- You need to update UI behavior during user interaction
How does the Frappe Form API work?
The Form API exposes a global object called frappe.ui.form, which is accessible inside DocType JavaScript controllers via the parameter frm.
A typical controller looks like:
frappe.ui.form.on('Sales Invoice', {
refresh(frm) {
// custom logic here
}
});
The frm object represents the current form instance, including:
- loaded document (frm.doc)
- metadata
- field definitions
- form events
- UI interaction functions
- API for child tables
Core Concepts in Form API
The Form API provides several categories of functions:
| Category | Purpose |
| Form Events | Lifecycle hooks |
| Field Operations | Set/get field values |
| Field Refresh | Reload UI components |
| Server Calls | Invoke Python whitelisted methods |
| UI Modifications | Control buttons and layout |
| Child Table API | Manage table rows |
| Validation | Prevent invalid submissions |
Form Lifecycle Events
What are form events?
Form events allow you to execute custom code at specific points in the form lifecycle.
Common events include:
| Event | Trigger |
| onload | Form loaded first time |
| refresh | Every time the form loads |
| validate | Before saving |
| before_save | Just before save request |
| after_save | After save completes |
| submit | When document is submitted |
| cancel | When canceled |
Example:
frappe.ui.form.on('Customer', {
refresh(frm) {
console.log("Form refreshed");
}
});
Accessing the Document
How do you access the form document?
Use frm.doc to read and update the current document.
Example:
let name = frm.doc.customer_name;
Update a field value:
frm.set_value('customer_name', 'John Doe');
Updating Field Values
How do you update a field from JavaScript?
Use the set_value function:
frm.set_value('status', 'Active');
To update multiple fields at once:
frm.set_value({
status: 'Active',
customer_group: 'Retail'
});
Refreshing Fields
After updating values, you may need to refresh the UI:
frm.refresh_field('status');
Refresh all fields:
frm.refresh();
Calling Python from Form API
How do you call a server method?
Use frappe.call() from JavaScript to execute a whitelisted Python function.
Python (whitelist)
@frappe.whitelist()
def compute_total(amount, qty):
return amount * qty
JavaScript
frappe.call({
method: 'myapp.api.compute_total',
args: {
amount: 100,
qty: 2
},
callback(r) {
frm.set_value('total', r.message);
}
});
Adding Custom Buttons
How do you add a button to the form?
Use the add_custom_button method inside refresh():
frm.add_custom_button('Compute', () => {
frappe.msgprint("Your logic here!");
});
Place button inside a group:
frm.add_custom_button('Compute', () => {}, 'Actions');
UI Controls
Hide or show fields
Hide:
frm.toggle_display('field_name', false);
Show:
frm.toggle_display('field_name', true);
Working With Child Tables
How do you add a row to a child table?
let row = frm.add_child('items', {
item_code: 'ITEM-0001',
qty: 5
});
frm.refresh_field('items');
Remove row:
frm.get_field('items').grid.remove(row);
frm.refresh_field('items');
Loop through rows:
frm.doc.items.forEach(row => {
console.log(row.item_code);
});
Client-side Validation
How do you stop the form from saving?
Use validation events:
frappe.ui.form.on('Customer', {
validate(frm) {
if (!frm.doc.mobile_no) {
frappe.throw("Mobile No is required");
}
}
});
Best Practices
- Use validation on client-side for immediate feedback
- Use server validation for final checks
- Prefer frm.set_value() over direct assignment
- Avoid long frappe.call() chains
- Keep UI logic separate from business logic
- Use whitelisted methods with parameters
- Never expose sensitive logic in JavaScript