Frappe Controls API Guide (v15)
What are Controls in Frappe?
In Frappe Framework, Controls are reusable UI components that power every field type in Desk forms. Each field in a DocType form—such as Data, Select, Link, Date, Check, Table, etc.—is rendered using an underlying control class, defined in the frontend layer.
Controls encapsulate rendering logic, state, validation, and interaction behavior, enabling developers to extend, override, or create custom field widgets in Frappe v15.
Controls live inside frappe.ui.form.Control* definitions.
Why are Controls important?
Controls are the foundation of the Desk UI. They:
- provide consistent UI for all field types
- implement behavior such as autocomplete, filtering, validation
- allow you to customize how fields are rendered
- enable creating custom field types without modifying core
- support complex layouts like Table, HTML, Section Break, Column Break
- Every field in a DocType is associated with a Control Class.
Example:
Data field uses frappe.ui.form.ControlData
Link field uses frappe.ui.form.ControlLink
Currency field uses frappe.ui.form.ControlCurrency
Where are Controls used?
Controls are used in the following contexts:
- DocType form pages
- Quick Entry dialogs
- Child Table grids
- Webform components
- Custom Scripts
- Form overridden controllers
- Custom widgets inside Desk
How Frappe Controls Work
Frappe controls are initialized dynamically based on the field definition from the DocType metadata. Each field’s construction flow:
- Get field definition from DocType meta
- Identify associated control type
- Initialize control class with field configuration
- Render HTML input widget
- Attach events and behaviors
- Bind UI state to frm.doc using set_value logic
Developer customizations happen by extending control classes.
Frappe Controls Architecture (v15)
The Controls system is defined under:
frappe/public/js/frappe/form/controls/
Each class extends the base class:
frappe.ui.form.Control
All control types are subclasses that implement:
- make_input() — create the UI element
- set_formatted_input() — set display value
- parse() — parse raw value
- validate() — validate value
- set_value() — update document & refresh
Built-in Frappe Control Classes
Frappe ships with a complete library of control types. Common examples include:
| Fieldtype | Control Class |
| Data | ControlData |
| Int | ControlInt |
| Float | ControlFloat |
| Currency | ControlCurrency |
| Select | ControlSelect |
| Link | ControlLink |
| Dynamic Link | ControlDynamicLink |
| Table | ControlTable |
| Date | ControlDate |
| Datetime | ControlDatetime |
| Time | ControlTime |
| Check | ControlCheck |
| Attach | ControlAttach |
| HTML | ControlHTML |
| Image | ControlImage |
| Signature | ControlSignature |
These control classes render all UI elements in Desk.
How to Extend a Frappe Control (Core Tutorial)
Step-by-step: Create a Custom Control
You can build your own field type by extending a control class.
1. Create a new JS file
Create inside your app:
your_app/public/js/custom_control.js
2. Extend the base control
frappe.ui.form.CustomControl = class CustomControl extends frappe.ui.form.ControlData {
make_input() {
super.make_input();
this.input.setAttribute('placeholder', 'Custom Input');
}
}
3. Bind Control to Fieldtype (hooks)
Add in hooks.py:
doctype_js = {
"*": "public/js/custom_control.js"
}
Or register a new fieldtype:
override_fieldtype = {
"Data": "frappe.ui.form.CustomControl"
}
4. Clear Cache
bench build
bench restart
Now all Data fields use the new logic.
How do Controls interact with Form API?
Controls are closely integrated with Form API functions:
frm.set_value() updates control value
frm.refresh_field() re-renders control UI
frm.toggle_display() hides a control
frm.trigger() executes events
Example:
frm.set_value('customer_name', 'John Doe');
frm.refresh_field('customer_name');
Under the hood, this calls:
ControlData.set_value()
Then re-renders the input UI.
Field-level Events in Controls
Controls expose events like:
- onchange
- onfocus
- onblur
- validate
- input
Attach custom behavior:
frm.fields_dict.customer_name.$input.on('input', function() {
console.log("Typing...");
});
Or:
frm.controls_dict.customer_name.df.onchange = function() {
console.log("Value changed");
}
Overriding Core Control Methods
You can override method behavior directly.
Example: validate email input:
frappe.ui.form.ControlData.prototype.validate = function(value) {
if (!value.includes('@')) {
frappe.throw('Invalid email!');
}
return value;
}
Controls in Child Tables
For child tables:
- UI is generated using Grid
- Each column uses a control class
- The table itself is ControlTable
Example: access child table control
let grid = frm.get_field('items').grid;
let control = grid.grid_rows[0].columns['item_code'].control;
Custom Rendering (Advanced Patterns)
Controls allow custom template rendering using make_wrapper():
make_wrapper() {
this.wrapper = $('<div class="custom-control">');
this.$wrapper.append(this.wrapper);
}
Custom rendering lets you create:
- Rich UI widgets
- Dynamic UI layouts
- Composite controls
- Progressive input components
Best Practices & Tips
Follow these guidelines for stable extensions:
- Never change core control files
- Extend using subclassing
- Keep control logic small & focused
- Use Custom Scripts for minor tweaks
- Use override hooks only when necessary
- Ensure browser compatibility
- Do not break consistency with ERPNext UI standards
Official References (Verified v15)
Frappe Docs:
https://docs.frappe.io/framework/user/en/api/controls
Frappe GitHub (version-15):