Get the FREE Ultimate OpenClaw Setup Guide →

erpnext-syntax-clientscripts

npx machina-cli add skill OpenAEC-Foundation/ERPNext_Anthropic_Claude_Development_Skill_Package/erpnext-syntax-clientscripts --openclaw
Files (1)
SKILL.md
5.4 KB

ERPNext 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

ActionCode
Set valuefrm.set_value('field', value)
Hide fieldfrm.toggle_display('field', false)
Make field mandatoryfrm.toggle_reqd('field', true)
Call serverfrappe.call({method: 'path.to.method', args: {}})
Prevent savefrappe.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

  1. ALWAYS call frm.refresh_field('table') after child table modifications
  2. NEVER use frm.doc.field = value — use frm.set_value()
  3. ALWAYS use __('text') for translatable strings
  4. validate event: use frappe.throw() to prevent save
  5. 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 trees
  • erpnext-errors-clientscripts — Error handling patterns
  • erpnext-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

  1. Step 1: Define your script with frappe.ui.form.on('DocType', { ... }).
  2. Step 2: Use frm.set_value, frm.toggle_display, and frm.toggle_reqd inside events.
  3. 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'); }

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers