Form Scripting in Frappe Framework

Introduction to Form Scripting in Frappe.

April 4, 2019

Frappe is a full-stack web application framework. It provides many views like the form view, tree view, report view, etc. Form view is used to enter data and hence needs to be very interactive. Javascript code is written to make this interactivity possible.

1. What Form Scripts Do

Form scripts help us to make forms interactive. They help us to apply business logic. If ‘X’ happens then ‘Y’ should happen kind of logic.
  1. A project might be linked to a company. In the Task when a project is chosen, the company can be set automatically.
  2. A Task might depend on the other tasks. However, these tasks must also belong to the same project. So the options shown in the ‘DEPENDS ON’ section must belong to the same project.
  3. We can also set a parent task, so the values shown in the parent task field should be those which are marked as a group task by enabling the checkbox ‘Is Group’.

2. What a Form Script Looks Like

Frappe framework offers an object named  frappe.ui.form.on and we can use the same to build above logic.
We need to pass three parameters to the above object

  1. DocType → In which DocType the code will execute
  2. Event or field → When the code will execute
  3. Function → What the

The above three parameters can be passed in either of the two formats shown below.

frappe.ui.form.on(string, object)

Where the string is a DocType and object is in “event: function” format.

Below is an example.

frappe.ui.form.on("Customer", { onload:function(frm)

The same code can also be written as below.

frappe.ui.form.on(string1, string2, function)

Where the string1 is a DocType, string2 is an event or field and function is a function.
Below is an example


3. Detailed Look at ‘Task’ Form Script

Let's take a look at the js code written for the ‘Task’ DocType.

  1. Below code provides the ‘erpnext.projects’ namespace
  1. Below code fetches the company from the project and sets it in the task.
cur_frm.add_fetch(“project”, “company”, “company”);
  1. Below is the outline of the code for various events and fields.
frappe.ui.form.on(“Task”, {
onload: function(frm) { //function_here },
refresh: function(frm) { //function_here },
setup: function(frm) { //function_here },
project: function(frm) { //function_here },
is_group: function(frm) { //function_here },
validate: function(frm) { //function_here },

Now let's look at the code for individual event or field.

Below code is executed as soon as the form is loaded and it sets the filter for values shown in ‘depends_on’ child table. The same task is excluded and tasks which belong to the same project are shown.

onload: function(frm){
frm.set_query("task", "depends_on", function() {
var filters = {
name: ["!=",]
if(frm.doc.project) filters["project"] = frm.doc.project; ` return {
filters: filters

Below code filters values shown in ‘parent_task’ field, if the form is local (has not been saved yet it) sets the exp_end_date to 7 days from the current date. Also, if the status is not equal to Completed then adds a button labeled “Completed” else adds a button labeled “Reopen”.

refresh: function(frm) {
frm.fields_dict['parent_task'].get_query = function() {
return {
filters: { "is_group": 1,
var doc = frm.doc;
if(doc.__islocal) {
if(!frm.doc.exp_end_date) {
if(!doc.__islocal) {
if(frm.perm[0].write) {
if(frm.doc.status!=="Completed" &&
frm.doc.status!=="Cancelled") {
frm.add_custom_button(__("Completed"), function() {
frm.set_value("status", "Completed");;
} else {
frm.add_custom_button(__("Reopen"), function() {
frm.set_value("status", "Open");;

Below code calls the ‘get_project’ function and the values returned by that function are shown in the ‘project’ field. This sort of code is generally used to filter the values shown in a field.

setup: function(frm) {
frm.fields_dict.project.get_query = function() {
return {
query: "erpnext.projects.doctype.task.task.get_project"

Below code will check if the current task has any child tasks and if so prevents the user from setting it as a non-parent task.

is_group: function (frm) {{
method:"erpnext.projects.doctype.task.task.check_if_child_exists", args: {
callback: function (r) {
if (r.message.length > 0) {
frappe.msgprint(__(`Cannot convert it to non-group. The following child Tasks exist: ${r.message.join(", ")}.`));

Below code invalidates the cache.

validate: function(frm) {
frm.doc.project && frappe.model.remove_from_locals("Project", frm.doc.project);

You can build the logic to be executed on the forms quickly using the events provided by the Frappe framework.

Happy Scripting!

Basawaraj Savalagi
Basawaraj Savalagi
"Basawaraj is a Lead Consultant at Frappe Technologies Pvt Ltd."
No comments yet

No comments yet. Start a new discussion.

Add Comment