Skip to main content

Form Scripts in Frappe Framework (Version 15)

Introduction — What Are Form Scripts in Frappe?

In the Frappe Framework, Form Scripts (also known as Client Scripts) are JavaScript files that allow you to control the behavior, interactivity, and validation of a form (DocType) directly from the client side.

They are executed inside the browser whenever a user opens, edits, or interacts with a document in Desk.

Using Form Scripts, developers can:

  • Auto-populate fields dynamically.
  • Validate data in real-time.
  • Create dependent dropdowns.
  • Trigger custom logic on form events (e.g., save, refresh, load).
  • Improve user experience with contextual automation.

Purpose of Form Scripts in Frappe v15

Form Scripts are designed to extend form functionality without modifying core code.
They serve as a bridge between the frontend interface and the backend logic of a DocType.

Example use cases:

  • Show a warning message when a specific condition is met.
  • Filter link fields dynamically.
  • Auto-set a default value based on the logged-in user.
  • Trigger custom alerts or notifications.

Location and Structure of Form Scripts

When you create a new DocType, Frappe automatically generates its associated JavaScript file in your app:

my_app/
└── my_app/
└── doctype/
└── task_tracker/
├── task_tracker.json
├── task_tracker.py
├── task_tracker.js ← Form Script file

This file is automatically loaded by Frappe when the corresponding form is opened.

Syntax and Structure of a Form Script

A typical Form Script follows the syntax:

frappe.ui.form.on("DocType Name", {
event_name: function(frm) {
// your custom logic
}
});

Where:

  • DocType Name → The target DocType (e.g., “Task Tracker”).
  • event_name → The event trigger (like refresh, validate, before_save).
  • frm → The Form object, which gives access to all document fields and methods.

Common Form Events in Frappe v15

Form Scripts can be bound to multiple client-side events. Below are the most commonly used ones:

Event Trigger Timing Example Use
refresh When the form is loaded or reloaded Custom UI logic or field toggles
validate Before saving the form Field validation
before_save Before saving but after validation Pre-save logic
after_save After form is saved Trigger notifications
onload When form is first loaded Fetch related data
onload_post_render After form is rendered Add custom buttons or DOM elements
before_submit Before submission Submission checks
on_submit After submission Trigger post-submission logic
on_cancel When a document is cancelled Cleanup or revert logic

Example — Basic Form Script

Example 1: Add Validation to a Form

frappe.ui.form.on("Task Tracker", {
validate: function(frm) {
if (!frm.doc.due_date) {
frappe.throw("Please set a Due Date before saving the Task.");
}
}
});

This script ensures that the “Due Date” field is filled before the form can be saved.

Example 2: Auto-Populate a Field Based on User

frappe.ui.form.on("Task Tracker", {
refresh: function(frm) {
if (!frm.doc.assigned_to) {
frm.set_value("assigned_to", frappe.session.user);
}
}
});

This automatically assigns the logged-in user to the “Assigned To” field if it’s empty.

Example 3: Filter Link Field Options Dynamically

frappe.ui.form.on("Sales Invoice", {
customer: function(frm) {
frm.set_query("contact_person", function() {
return {
filters: {
customer: frm.doc.customer
}
};
});
}
});

When a Customer is selected, only related Contact Persons appear in the dropdown.

Form Script API — The frm Object

The frm object is a powerful interface provided by Frappe to interact with the form, its fields, and metadata.

Commonly Used frm Methods

Method Description Example
frm.set_value(fieldname, value) Set a field’s value programmatically frm.set_value(“status”, “Completed”)
frm.refresh_field(fieldname) Refresh a specific field on the form frm.refresh_field(“status”)
frm.toggle_enable(fieldname, boolean) Enable or disable a field frm.toggle_enable(“due_date”, false)
frm.toggle_reqd(fieldname, boolean) Make a field mandatory or optional frm.toggle_reqd(“remarks”, true)
frm.add_custom_button(label, function) Add a custom button on form header frm.add_custom_button(“Mark Done”, () => {…})
frm.save() Save the document via client script frm.save()
frm.reload_doc() Reload form data from the server frm.reload_doc()

Dynamic Field Manipulation

Frappe Form Scripts can modify field visibility, requirements, and permissions dynamically.

Example: Toggle Visibility

frappe.ui.form.on("Employee", {
refresh: function(frm) {
frm.toggle_display("resignation_date", frm.doc.status === "Resigned");
}
});

Example: Make Field Read-Only

frm.set_df_property("salary", "read_only", 1);

Custom Buttons and Actions

You can add custom buttons to perform specific tasks directly from the form toolbar.

frappe.ui.form.on("Project", {
refresh: function(frm) {
frm.add_custom_button("View Tasks", function() {
frappe.set_route("List", "Task", {"project": frm.doc.name});
});
}
});

When clicked, this opens a filtered Task List view for the current project.

Advanced Example — Conditional Logic and Server Call

Form Scripts can also communicate with server-side methods using Frappe’s call API.

frappe.ui.form.on("Expense Claim", {
before_submit: function(frm) {
frappe.call({
method: "erpnext.hr.doctype.expense_claim.expense_claim.check_budget_limit",
args: {
employee: frm.doc.employee,
amount: frm.doc.total_amount
},
callback: function(response) {
if (!response.message) {
frappe.throw("Budget limit exceeded for this expense!");
}
}
});
}
});

Explanation:

  • Executes a Python method before submission.
  • Sends data to the backend using frappe.call.
  • Throws an error if a budget constraint is violated.

Integration with Custom Scripts (Desk)

Instead of editing .js files manually, you can add client-side logic through the Custom Script feature in Desk:

Path:

Settings → Customization → Custom Script

Fields:

  • Doctype: The target DocType.
  • Script Type: Client.
  • Script: The JavaScript code block.

This approach is ideal for administrators or ERP implementers who don’t have filesystem access.

Best Practices for Writing Form Scripts

  • Keep scripts modular and specific to a DocType.
  • Use console.log() for debugging and testing logic.
  • Avoid long-running loops or server-heavy logic in client scripts.
  • Prefer backend validation for critical business logic.
  • Cache frequently accessed fields for faster performance.
  • Always test in both Desk and Web Forms if applicable.

Troubleshooting Common Issues

Issue Cause Solution
Script not executing Incorrect DocType name or syntax error Verify DocType spelling and check browser console for errors
Form reloads infinitely Recursive call of frm.save() inside save event Avoid calling frm.save() inside validate or before_save
Field not updating visually Field not refreshed Use frm.refresh_field(“fieldname”) after setting value
Permission error on frappe.call Unauthorized method or missing permissions Use whitelisted server methods with @frappe.whitelist() decorator

Cross-References and Related Topics

  • Controller Methods in Frappe Framework (v15)
  • DocType Features in Frappe Framework (v15)
  • Client and Server Script Integration
  • Frappe GitHub Repository (v15)
Click to rate this post!
[Total: 0 Average: 0]