erpnext-syntax-clientscripts
npx machina-cli add skill OpenAEC-Foundation/ERPNext_Anthropic_Claude_Development_Skill_Package/erpnext-syntax-clientscripts --openclawERPNext Client Scripts Syntax (EN)
Client Scripts run in the browser and control all UI interactions in ERPNext/Frappe. They are created via Setup → Client Script or in custom apps under public/js/.
Version: v14/v15/v16 compatible (unless noted otherwise)
Quick Reference
Basic Structure
frappe.ui.form.on('DocType Name', {
// Form-level events
setup(frm) { },
refresh(frm) { },
validate(frm) { },
// Field change events
fieldname(frm) { }
});
Most Used Patterns
| Action | Code |
|---|---|
| Set value | frm.set_value('field', value) |
| Hide field | frm.toggle_display('field', false) |
| Make field mandatory | frm.toggle_reqd('field', true) |
| Call server | frappe.call({method: 'path.to.method', args: {}}) |
| Prevent save | frappe.throw('Error message') |
Event Selection
Which event should I use?
One-time setup (queries, defaults)?
└── setup
Show/hide UI, add buttons?
└── refresh
Validation before save?
└── validate
Do something right after save?
└── after_save
React to field change?
└── {fieldname}
→ See references/events.md for complete event list and execution order.
Essential Methods
Value Manipulation
// Set single value (async, returns Promise)
frm.set_value('status', 'Approved');
// Set multiple values at once
frm.set_value({
status: 'Approved',
priority: 'High'
});
// Get value
let value = frm.doc.fieldname;
Field Properties
// Show/hide
frm.toggle_display('priority', condition);
// Make mandatory
frm.toggle_reqd('due_date', true);
// Make read-only
frm.toggle_enable('amount', false);
// Advanced property change
frm.set_df_property('status', 'options', ['New', 'Open', 'Closed']);
frm.set_df_property('amount', 'read_only', 1);
Link Field Filters
// Simple filter
frm.set_query('customer', () => ({
filters: { disabled: 0 }
}));
// Filter in child table
frm.set_query('item_code', 'items', (doc, cdt, cdn) => ({
filters: { is_sales_item: 1 }
}));
→ See references/methods.md for complete method signatures.
Server Communication
frappe.call (Whitelisted Methods)
frappe.call({
method: 'myapp.api.process_data',
args: { customer: frm.doc.customer },
freeze: true,
freeze_message: __('Processing...'),
callback: (r) => {
if (r.message) {
frm.set_value('result', r.message);
}
}
});
frm.call (Document Methods)
// Calls method on document controller
frm.call('calculate_taxes', { include_shipping: true })
.then(r => frm.reload_doc());
Async/Await Pattern
async function fetchData(frm) {
let r = await frappe.call({
method: 'frappe.client.get_value',
args: {
doctype: 'Customer',
filters: { name: frm.doc.customer },
fieldname: 'credit_limit'
}
});
return r.message.credit_limit;
}
Child Table Handling
Adding Rows
let row = frm.add_child('items', {
item_code: 'ITEM-001',
qty: 5,
rate: 100
});
frm.refresh_field('items'); // REQUIRED after modification
Editing Rows
frm.doc.items.forEach((row) => {
if (row.qty > 10) {
row.discount_percentage = 5;
}
});
frm.refresh_field('items');
Child Table Events
frappe.ui.form.on('Sales Invoice Item', {
qty(frm, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
frappe.model.set_value(cdt, cdn, 'amount', row.qty * row.rate);
},
items_add(frm, cdt, cdn) {
// New row added
},
items_remove(frm) {
// Row removed
}
});
→ See references/examples.md for complete child table examples.
Custom Buttons
frappe.ui.form.on('Sales Order', {
refresh(frm) {
if (frm.doc.docstatus === 1) {
// Grouped buttons
frm.add_custom_button(__('Invoice'), () => {
// action
}, __('Create'));
// Primary action
frm.page.set_primary_action(__('Process'), () => {
frm.call('process').then(() => frm.reload_doc());
});
}
}
});
Critical Rules
- ALWAYS call
frm.refresh_field('table')after child table modifications - NEVER use
frm.doc.field = value— usefrm.set_value() - ALWAYS use
__('text')for translatable strings - validate event: use
frappe.throw()to prevent save - setup event: only for one-time configuration (not repeated)
→ See references/anti-patterns.md for common mistakes.
Related Skills
erpnext-impl-clientscripts— Implementation workflows and decision treeserpnext-errors-clientscripts— Error handling patternserpnext-syntax-whitelisted— Server-side methods to call
Source
git clone https://github.com/OpenAEC-Foundation/ERPNext_Anthropic_Claude_Development_Skill_Package/blob/main/skills/source/syntax/erpnext-syntax-clientscripts/SKILL.mdView on GitHub Overview
Learn the exact JavaScript patterns used for ERPNext/Frappe Client Scripts. Use this skill when writing client-side code for form events, field manipulation, server calls, or child table handling in ERPNext v14/v15/v16. It covers triggers like client script, frm methods, and frappe.ui.form.on to drive browser-side UI interactions and validation.
How This Skill Works
Client scripts run in the browser to control ERPNext UI. Use frappe.ui.form.on to listen for form and field events (setup, refresh, validate, and field-specific events) and then manipulate fields with frm.set_value, frm.toggle_display, and frm.toggle_reqd. For server interaction use frappe.call (and frm.call for document methods) and async/await patterns; manage child tables with add_child and refresh_field.
When to Use It
- Initialize defaults or run one-time queries on form load (setup).
- Show/hide UI elements or add buttons during refresh.
- Validate data before saving (validate).
- React to changes in a specific field (fieldname events).
- Work with and modify rows in a child table (e.g., items) and keep it in sync.
Quick Start
- Step 1: Define your script with frappe.ui.form.on('DocType', { ... }).
- Step 2: Use frm.set_value, frm.toggle_display, and frm.toggle_reqd inside events.
- Step 3: Call server data with frappe.call or frm.call and refresh fields as needed.
Best Practices
- Use a single, DocType scoped frappe.ui.form.on block to organize events.
- Prefer frm.set_value for updates and async/await with frappe.call for server calls.
- Use frm.toggle_display, frm.toggle_reqd, and frm.set_df_property for UI changes.
- Always refresh fields after programmatic changes (refresh_field) and handle errors gracefully.
- Keep client scripts lightweight and rely on ERPNext APIs rather than direct DOM manipulation.
Example Use Cases
- Initialize defaults on form load: setup(frm) { frm.set_value('status', 'New'); }
- Show/hide fields based on a condition: refresh(frm) { frm.toggle_display('credit_limit', !!frm.doc.customer); }
- Fetch server data and populate a field: frappe.call({ method: 'myapp.api.get_credit', args: { customer: frm.doc.customer } }).then(r => frm.set_value('credit_limit', r.message));
- Add a row to a child table and refresh: let row = frm.add_child('items', { item_code: 'ITEM-001', qty: 5 }); frm.refresh_field('items');
- Validate before save: validate(frm) { if (!frm.doc.customer) frappe.throw('Customer is required'); }