Frappe Client Scripts Reference
Complete reference for client-side JavaScript development in Frappe Framework.
When to Use This Skill
-
Writing form scripts (refresh, validate, field events)
-
Manipulating form fields (show/hide, require, read-only)
-
Creating dialogs and prompts
-
Making API calls from client
-
Customizing list views
-
Adding custom buttons
-
Handling child table events
Form Script Location
my_app/ └── my_module/ └── doctype/ └── my_doctype/ └── my_doctype.js # Client script
Form Events
Complete Event Reference
frappe.ui.form.on('My DocType', { // === LOAD EVENTS ===
setup: function(frm) {
// Called once when form is created (before data loads)
// Use for: setting queries, initializing variables
frm.set_query('customer', () => ({ filters: { status: 'Active' } }));
},
onload: function(frm) {
// Called when form data is loaded (before refresh)
// Use for: setting defaults for new docs
if (frm.is_new()) {
frm.set_value('posting_date', frappe.datetime.nowdate());
}
},
onload_post_render: function(frm) {
// Called after form is rendered
// Use for: DOM manipulation, focus setting
frm.get_field('customer').focus();
},
refresh: function(frm) {
// Called every time form refreshes
// Use for: custom buttons, field toggles, indicators
if (!frm.is_new()) {
frm.add_custom_button(__('Action'), () => do_action(frm));
}
frm.toggle_display('section_name', frm.doc.show_section);
},
// === SAVE EVENTS ===
validate: function(frm) {
// Called before save - return false to prevent
if (frm.doc.end_date < frm.doc.start_date) {
frappe.msgprint(__('End Date cannot be before Start Date'));
return false;
}
},
before_save: function(frm) {
// Called after validate, before server request
frm.doc.last_updated_by = frappe.session.user;
},
after_save: function(frm) {
// Called after successful save
frappe.show_alert({
message: __('Saved successfully'),
indicator: 'green'
});
},
// === WORKFLOW EVENTS ===
before_submit: function(frm) {
// Called before document submission
},
on_submit: function(frm) {
// Called after successful submission
},
before_cancel: function(frm) {
// Called before cancellation
},
after_cancel: function(frm) {
// Called after cancellation
},
// === FIELD EVENTS ===
customer: function(frm) {
// Called when 'customer' field changes
if (frm.doc.customer) {
fetch_customer_details(frm);
}
},
posting_date: function(frm) {
// Called when 'posting_date' field changes
calculate_due_date(frm);
}
});
Field Manipulation
Display Properties
// Show/hide field frm.toggle_display('fieldname', true); // Show frm.toggle_display('fieldname', false); // Hide frm.toggle_display(['field1', 'field2'], condition);
// Set read-only frm.set_df_property('fieldname', 'read_only', 1); frm.toggle_enable('fieldname', false); // Disable
// Set required frm.set_df_property('fieldname', 'reqd', 1); frm.toggle_reqd('fieldname', true); frm.toggle_reqd(['field1', 'field2'], condition);
// Set hidden frm.set_df_property('fieldname', 'hidden', 1);
// Change label frm.set_df_property('fieldname', 'label', 'New Label');
// Change description frm.set_df_property('fieldname', 'description', 'Help text');
// Change options (for Select) frm.set_df_property('fieldname', 'options', 'Option1\nOption2\nOption3');
// Refresh after changes frm.refresh_field('fieldname'); frm.refresh_fields();
Set Values
// Set single value frm.set_value('fieldname', value);
// Set multiple values frm.set_value({ 'field1': 'value1', 'field2': 'value2', 'field3': 'value3' });
// Set with callback frm.set_value('fieldname', value).then(() => { // After value is set });
// Clear field frm.set_value('fieldname', null); frm.set_value('fieldname', '');
// Set default value frm.set_df_property('fieldname', 'default', 'default_value');
Link Field Queries
// Basic filter frm.set_query('customer', function() { return { filters: { status: 'Active', customer_type: 'Company' } }; });
// Dynamic filter based on form values frm.set_query('item_code', function() { return { filters: { item_group: frm.doc.item_group, is_stock_item: 1 } }; });
// Filter in child table frm.set_query('item_code', 'items', function(doc, cdt, cdn) { let row = locals[cdt][cdn]; return { filters: { warehouse: row.warehouse || doc.default_warehouse } }; });
// Custom query (server method) frm.set_query('supplier', function() { return { query: 'my_app.api.get_suppliers', filters: { region: frm.doc.region } }; });
// Clear query frm.set_query('fieldname', null);
Custom Buttons
refresh: function(frm) { // Simple button frm.add_custom_button(__('Do Something'), function() { do_something(frm); });
// Button in group/dropdown
frm.add_custom_button(__('Action 1'), function() {
action_1(frm);
}, __('Actions'));
frm.add_custom_button(__('Action 2'), function() {
action_2(frm);
}, __('Actions'));
// Primary button (highlighted)
frm.add_custom_button(__('Submit'), function() {
submit_doc(frm);
}).addClass('btn-primary');
// Button with icon
let btn = frm.add_custom_button(__('Print'), function() {
print_doc(frm);
});
btn.prepend('<i class="fa fa-print"></i> ');
// Conditional buttons
if (frm.doc.status === 'Draft') {
frm.add_custom_button(__('Submit for Review'), function() {
submit_for_review(frm);
});
}
// Remove button
frm.remove_custom_button(__('Do Something'));
frm.remove_custom_button(__('Action 1'), __('Actions'));
// Clear all buttons
frm.clear_custom_buttons();
// Page actions
frm.page.set_primary_action(__('Save'), function() {
frm.save();
});
frm.page.set_secondary_action(__('Cancel'), function() {
frappe.set_route('List', 'My DocType');
});
}
Child Table Operations
Events
frappe.ui.form.on('My DocType Item', { // Row added items_add: function(frm, cdt, cdn) { let row = locals[cdt][cdn]; row.warehouse = frm.doc.default_warehouse; frm.refresh_field('items'); },
// Before row removed (can prevent)
before_items_remove: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.is_mandatory) {
frappe.throw(__('Cannot remove mandatory item'));
}
},
// Row removed
items_remove: function(frm, cdt, cdn) {
calculate_total(frm);
},
// Field in row changes
qty: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.amount = flt(row.qty) * flt(row.rate);
frm.refresh_field('items');
calculate_total(frm);
},
rate: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.amount = flt(row.qty) * flt(row.rate);
frm.refresh_field('items');
calculate_total(frm);
},
item_code: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (row.item_code) {
frappe.call({
method: 'my_app.api.get_item_details',
args: { item_code: row.item_code },
callback: function(r) {
if (r.message) {
frappe.model.set_value(cdt, cdn, {
'rate': r.message.rate,
'uom': r.message.uom,
'description': r.message.description
});
}
}
});
}
}
});
function calculate_total(frm) { let total = 0; frm.doc.items.forEach(item => { total += flt(item.amount); }); frm.set_value('total', total); }
Manipulating Rows
// Add row let row = frm.add_child('items', { item_code: 'ITEM-001', qty: 10, rate: 100 }); frm.refresh_field('items');
// Get row by index let first_row = frm.doc.items[0];
// Get row by name let row = locals['My DocType Item'][cdn];
// Update row frappe.model.set_value(cdt, cdn, 'fieldname', value); frappe.model.set_value(cdt, cdn, { 'field1': 'value1', 'field2': 'value2' });
// Remove row frm.get_field('items').grid.grid_rows[0].remove(); frm.refresh_field('items');
// Remove all rows frm.clear_table('items'); frm.refresh_field('items');
// Iterate rows frm.doc.items.forEach((item, idx) => { console.log(idx, item.item_code); });
Dialogs
Simple Prompt
// Single field frappe.prompt( { fieldname: 'reason', fieldtype: 'Small Text', label: 'Reason', reqd: 1 }, function(values) { console.log(values.reason); }, __('Enter Reason'), __('Submit') );
Multi-field Prompt
frappe.prompt([ { fieldname: 'customer', fieldtype: 'Link', options: 'Customer', label: 'Customer', reqd: 1 }, { fieldname: 'date', fieldtype: 'Date', label: 'Date', default: frappe.datetime.nowdate() }, { fieldname: 'priority', fieldtype: 'Select', label: 'Priority', options: 'Low\nMedium\nHigh', default: 'Medium' } ], function(values) { process_data(values); }, __('Enter Details'), __('Process'));
Custom Dialog
let dialog = new frappe.ui.Dialog({ title: __('Custom Dialog'), fields: [ { fieldname: 'customer', fieldtype: 'Link', options: 'Customer', label: __('Customer'), reqd: 1, get_query: function() { return { filters: { status: 'Active' } }; }, change: function() { // Field change handler let value = dialog.get_value('customer'); if (value) { dialog.set_value('customer_name', 'Loading...'); } } }, { fieldtype: 'Column Break' }, { fieldname: 'customer_name', fieldtype: 'Data', label: __('Customer Name'), read_only: 1 }, { fieldtype: 'Section Break', label: 'Items' }, { fieldname: 'items', fieldtype: 'Table', label: __('Items'), cannot_add_rows: false, in_place_edit: true, fields: [ { fieldname: 'item', fieldtype: 'Link', options: 'Item', in_list_view: 1, label: __('Item') }, { fieldname: 'qty', fieldtype: 'Float', in_list_view: 1, label: __('Qty') } ] } ], size: 'large', // small, large, extra-large primary_action_label: __('Submit'), primary_action: function(values) { console.log(values); dialog.hide(); process_dialog(values); }, secondary_action_label: __('Cancel') });
dialog.show();
// Set values dialog.set_value('customer', 'CUST-001'); dialog.set_values({ 'customer': 'CUST-001', 'date': frappe.datetime.nowdate() });
// Get values let values = dialog.get_values(); let customer = dialog.get_value('customer');
// Access fields let field = dialog.get_field('customer'); field.set_description('Select active customer');
Confirmation Dialog
frappe.confirm( __('Are you sure you want to delete this?'), function() { // On Yes delete_record(); }, function() { // On No (optional) } );
API Calls
frappe.call
// Basic call frappe.call({ method: 'my_app.api.get_data', args: { customer: frm.doc.customer }, callback: function(r) { if (r.message) { frm.set_value('data', r.message); } } });
// With loading indicator frappe.call({ method: 'my_app.api.process', args: { data: frm.doc }, freeze: true, freeze_message: ('Processing...'), callback: function(r) { frappe.msgprint(('Done!')); }, error: function(r) { frappe.msgprint(__('Error occurred')); } });
// Async/await async function getData() { const r = await frappe.call({ method: 'my_app.api.get_data', args: { id: 123 } }); return r.message; }
// Promise chain frappe.call({ method: 'my_app.api.get_data' }).then(r => { return frappe.call({ method: 'my_app.api.process', args: { data: r.message } }); }).then(r => { console.log('Done', r.message); });
Messages & Alerts
// Toast alert frappe.show_alert({ message: __('Success!'), indicator: 'green' // green, blue, orange, red }, 5); // seconds
// Message dialog frappe.msgprint({ title: __('Information'), message: __('This is important'), indicator: 'blue' });
// Error (stops execution) frappe.throw(__('Cannot proceed'));
// Confirmation required frappe.validated = false; // In validate event
Utilities
// Date/Time frappe.datetime.nowdate(); // "2024-01-15" frappe.datetime.now_datetime(); // "2024-01-15 10:30:00" frappe.datetime.add_days("2024-01-15", 7); frappe.datetime.add_months("2024-01-15", 1);
// Formatting frappe.format(1234.56, {fieldtype: 'Currency'}); format_currency(1234.56, 'USD'); flt(value); // Float cint(value); // Integer
// Navigation frappe.set_route('Form', 'Customer', 'CUST-001'); frappe.set_route('List', 'Customer'); frappe.new_doc('Customer');
// Translation __('Translate this'); __('Hello {0}', [name]);