Skip to main content

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:

  1. Get field definition from DocType meta
  2. Identify associated control type
  3. Initialize control class with field configuration
  4. Render HTML input widget
  5. Attach events and behaviors
  6. 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):

https://github.com/frappe/frappe/tree/version-15

Click to rate this post!
[Total: 0 Average: 0]