AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Frappe Client Script Generator

skill-venkateshvenki404224-frappe-apps-manager-frappe-client-script-generator · by Venkateshvenki404224

Generate JavaScript client-side form scripts for Frappe DocTypes. Use when creating form customizations, field validations, custom buttons, or client-side logic for Frappe/ERPNext forms.

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-venkateshvenki404224-frappe-apps-manager-frappe-client-script-generator

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-venkateshvenki404224-frappe-apps-manager-frappe-client-script-generator)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Frappe Client Script Generator? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Frappe Client Script Generator

Generate production-ready JavaScript form scripts for Frappe DocTypes with proper event handlers, validations, and custom functionality.

Global Rules

These Frappe conventions apply to everything this skill generates, and override any conflicting example below.

  • Bench commands: use bare bench (never ./env/bin/bench or a full path). Always pass --site explicitly — never run a bare bench migrate / bench run-tests. Run bench start in the background and only if it isn't already running. Don't run discovery commands (which bench, bench --version).
  • DocType files live at apps////doctype//.json — the app name appears twice (directory + Python package) — with an empty __init__.py alongside. Never mkdir the folder; write the JSON and run bench --site migrate to create the structure. Don't add creation, modified, owner, modified_by, or docstatus as fields — Frappe manages them.
  • Database & ORM: prefer frappe.qb.get_query() over raw frappe.db.sql(). Use frappe.db.get_all() for server logic (ignores permissions) and frappe.db.get_list() for user-facing APIs (enforces them). Never use frappe.db.set_value() on a field with validation or lifecycle logic — load the doc and doc.save() so controller hooks run. Batch-fetch related records; never query inside a loop (N+1).
  • Never call frappe.db.commit() in controllers, request handlers, background jobs, or patches — Frappe auto-commits on success and rolls back on uncaught errors. Flush manually only to make a write visible to a subsequent frappe.enqueue() (or pass enqueue_after_commit=True).
  • Permissions & APIs: put permission checks inside controller methods (enforced on every call path), not in API wrappers. Type-hint every @frappe.whitelist() parameter so Frappe validates and casts it, and pass methods=[...] to pin the HTTP verb.

When to Use This Skill

Claude should invoke this skill when:

  • User wants to add client-side form customizations
  • User needs field validations or calculations
  • User requests custom buttons or actions on forms
  • User wants to filter or fetch data dynamically
  • User mentions form scripts, client scripts, or JavaScript for DocTypes
  • User wants to show/hide fields conditionally
  • User needs to set field values based on other fields

Capabilities

1. Form Event Handlers

Generate event handlers for DocType forms following Frappe patterns from core apps.

Refresh Event (runs when form loads):

// Pattern from: erpnext/accounts/doctype/sales_invoice/sales_invoice.js
frappe.ui.form.on('Sales Invoice', {
    refresh: function(frm) {
        // Add custom buttons
        if (frm.doc.docstatus === 1) {
            frm.add_custom_button(__('Create Payment'), function() {
                frm.events.make_payment_entry(frm);
            });
        }

        // Set field properties
        frm.set_df_property('customer', 'reqd', 1);

        // Show/hide fields
        frm.toggle_display('discount_section', frm.doc.apply_discount);
    }
});

Setup Event (runs once when form is created):

// Pattern from: erpnext/stock/doctype/stock_entry/stock_entry.js
frappe.ui.form.on('Stock Entry', {
    setup: function(frm) {
        // Set query filters for Link fields
        frm.set_query('item_code', 'items', function() {
            return {
                filters: {
                    'is_stock_item': 1,
                    'has_serial_no': 0
                }
            };
        });
    }
});

Onload Event (runs on form load, before refresh):

// Pattern from: erpnext/accounts/doctype/payment_entry/payment_entry.js
frappe.ui.form.on('Payment Entry', {
    onload: function(frm) {
        if (frm.is_new()) {
            frm.set_value('posting_date', frappe.datetime.get_today());
        }
    }
});

2. Field Change Handlers

Single Field Change:

// Pattern from: erpnext/selling/doctype/sales_order/sales_order.js
frappe.ui.form.on('Sales Order', {
    customer: function(frm) {
        if (frm.doc.customer) {
            // Fetch customer details
            frappe.db.get_value('Customer', frm.doc.customer, 'customer_group')
                .then(r => {
                    if (r.message) {
                        frm.set_value('customer_group', r.message.customer_group);
                    }
                });
        }
    }
});

Multiple Field Dependencies:

// Pattern from: erpnext/accounts/doctype/sales_invoice/sales_invoice.js
frappe.ui.form.on('Sales Invoice', {
    customer: function(frm) {
        frm.events.set_dynamic_field_label(frm);
    },
    currency: function(frm) {
        frm.events.set_dynamic_field_label(frm);
    },
    set_dynamic_field_label: function(frm) {
        if (frm.doc.currency) {
            frm.set_currency_labels(['total', 'grand_total'], frm.doc.currency);
        }
    }
});

3. Child Table (Grid) Events

Child Table Row Events:

// Pattern from: erpnext/accounts/doctype/sales_invoice/sales_invoice_item.js
frappe.ui.form.on('Sales Invoice Item', {
    item_code: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.item_code) {
            frappe.call({
                method: 'erpnext.stock.get_item_details.get_item_details',
                args: {
                    item_code: row.item_code,
                    company: frm.doc.company
                },
                callback: function(r) {
                    if (r.message) {
                        frappe.model.set_value(cdt, cdn, 'rate', r.message.price_list_rate);
                        frappe.model.set_value(cdt, cdn, 'uom', r.message.stock_uom);
                    }
                }
            });
        }
    },

    qty: function(frm, cdt, cdn) {
        frm.events.calculate_totals(frm, cdt, cdn);
    },

    rate: function(frm, cdt, cdn) {
        frm.events.calculate_totals(frm, cdt, cdn);
    }
});

Grid Operations:

// Pattern from: erpnext/stock/doctype/stock_entry/stock_entry.js
frappe.ui.form.on('Stock Entry', {
    items_add: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.s_warehouse = frm.doc.from_warehouse;
        row.t_warehouse = frm.doc.to_warehouse;
    },

    items_remove: function(frm) {
        frm.events.calculate_totals(frm);
    }
});

4. Custom Buttons and Actions

Standard Button Patterns:

// Pattern from: erpnext/accounts/doctype/sales_invoice/sales_invoice.js
frappe.ui.form.on('Sales Invoice', {
    refresh: function(frm) {
        if (frm.doc.docstatus === 1 && frm.doc.outstanding_amount > 0) {
            frm.add_custom_button(__('Payment'), function() {
                frm.events.make_payment_entry(frm);
            }, __('Create'));
        }

        // Add custom button in toolbar
        if (frm.doc.docstatus === 0) {
            frm.add_custom_button(__('Get Items from Sales Order'), function() {
                erpnext.utils.map_current_doc({
                    method: 'erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice',
                    source_doctype: 'Sales Order',
                    target: frm,
                    setters: {
                        customer: frm.doc.customer || undefined
                    },
                    get_query_filters: {
                        docstatus: 1,
                        status: ['not in', ['Closed', 'On Hold']]
                    }
                });
            });
        }
    },

    make_payment_entry: function(frm) {
        return frappe.call({
            method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry',
            args: {
                dt: frm.doc.doctype,
                dn: frm.doc.name
            },
            callback: function(r) {
                let doc = frappe.model.sync(r.message);
                frappe.set_route('Form', doc[0].doctype, doc[0].name);
            }
        });
    }
});

5. Data Fetching and API Calls

> Two ways to reach the server. Use frm.call("") to invoke a @frappe.whitelist() method on the current DocType controller (Frappe passes the document automatically). Use frappe.call({ method, args }) for a standalone whitelisted module function. In both cases the args keys and values must match the method's type-hinted parameter signature — Frappe validates and casts each argument against those hints, so a mismatch will raise.

// Controller method: @frappe.whitelist() def get_summary(self) on the Expense DocType
frm.call("get_summary").then(r => frappe.msgprint(r.message));

// Standalone whitelisted function: def get_expenses(status: str)
frappe.call({
    method: 'my_app.api.get_expenses',
    args: { status: 'Draft' },   // names/types must match the Python signature
    callback: r => console.log(r.message)
});

Fetch from Database:

// Pattern from: erpnext/stock/doctype/item/item.js
frappe.ui.form.on('Item', {
    item_group: function(frm) {
        if (frm.doc.item_group) {
            frappe.db.get_value('Item Group', frm.doc.item_group, 'default_warehouse')
                .then(r => {
                    if (r.message && r.message.default_warehouse) {
                        frm.set_value('default_warehouse', r.message.default_warehouse);
                    }
                });
        }
    }
});

Server Method Calls:

// Pattern from: erpnext/accounts/doctype/payment_entry/payment_entry.js
frappe.ui.form.on('Payment Entry', {
    party: function(frm) {
        if (frm.doc.party_type && frm.doc.party) {
            frappe.call({
                method: 'erpnext.accounts.party.get_party_details',
                args: {
                    party: frm.doc.party,
                    party_type: frm.doc.party_type,
                    company: frm.doc.company
                },
                callback: function(r) {
                    if (r.message) {
                        frm.set_value('party_name', r.message.party_name);
                        frm.set_value('party_account', r.message.party_account);
                    }
                }
            });
        }
    }
});

6. Form Validations

> Client-side validation is UX only. It gives the user immediate feedback but can be bypassed (API calls, scripts, imports). The authoritative validation must live in the Python controller's validate() method. Mirror critical rules in both places, but never rely on the client script alone to enforce data integrity.

Before Save Validation:

// Pattern from: erpnext/accounts/doctype/sales_invoice/sales_invoice.js
frappe.ui.form.on('Sales Invoice', {
    validate: function(frm) {
        // Validate posting date
        if (frm.doc.posting_date > frappe.datetime.get_today()) {
            frappe.throw(__('Posting Date cannot be future date'));
        }

        // Validate items
        if (!frm.doc.items || frm.doc.items.length === 0) {
            frappe.throw(__('Please add at least one item'));
        }

        // Validate total
        if (frm.doc.grand_total  0) {
                has_qty = true;
            }
        });

        if (!has_qty) {
            frappe.throw(__('Please enter quantity for at least one item'));
        }
    }
});

7. Conditional Field Display

Show/Hide Fields:

// Pattern from: erpnext/accounts/doctype/payment_entry/payment_entry.js
frappe.ui.form.on('Payment Entry', {
    payment_type: function(frm) {
        frm.events.toggle_fields(frm);
    },

    toggle_fields: function(frm) {
        let is_receive = (frm.doc.payment_type === 'Receive');
        let is_pay = (frm.doc.payment_type === 'Pay');

        frm.toggle_display('paid_from', is_pay);
        frm.toggle_display('paid_to', is_receive);
        frm.toggle_reqd('paid_from', is_pay);
        frm.toggle_reqd('paid_to', is_receive);
    }
});

Field Property Changes:

// Pattern from: erpnext/selling/doctype/sales_order/sales_order.js
frappe.ui.form.on('Sales Order', {
    refresh: function(frm) {
        // Make field read-only based on condition
        frm.set_df_property('customer', 'read_only', frm.doc.docstatus === 1);

        // Change field label
        frm.set_df_property('delivery_date', 'label',
            frm.doc.order_type === 'Sales' ? __('Delivery Date') : __('Delivery By'));

        // Set field as mandatory
        frm.toggle_reqd('delivery_date', frm.doc.order_type === 'Sales');
    }
});

8. Calculations and Totals

Calculate Child Table Totals:

// Pattern from: erpnext/accounts/doctype/sales_invoice/sales_invoice.js
frappe.ui.form.on('Sales Invoice', {
    calculate_totals: function(frm) {
        let total = 0;
        frm.doc.items.forEach(function(item) {
            item.amount = flt(item.qty) * flt(item.rate);
            total += item.amount;
        });
        frm.set_value('total', total);

        // Calculate tax and grand total
        let tax_amount = flt(total * frm.doc.tax_rate / 100);
        frm.set_value('total_taxes_and_charges', tax_amount);
        frm.set_value('grand_total', total + tax_amount);
    }
});

frappe.ui.form.on('Sales Invoice Item', {
    qty: function(frm, cdt, cdn) {
        let item = locals[cdt][cdn];
        frappe.model.set_value(cdt, cdn, 'amount',
            flt(item.qty) * flt(item.rate));
        frm.events.calculate_totals(frm);
    },

    rate: function(frm, cdt, cdn) {
        let item = locals[cdt][cdn];
        frappe.model.set_value(cdt, cdn, 'amount',
            flt(item.qty) * flt(item.rate));
        frm.events.calculate_totals(frm);
    }
});

9. Link Field Filters (set_query)

Filter Link Field Options:

// Pattern from: erpnext/stock/doctype/stock_entry/stock_entry.js
frappe.ui.form.on('Stock Entry', {
    setup: function(frm) {
        // Filter items based on item group
        frm.set_query('item_code', 'items', function(doc, cdt, cdn) {
            return {
                filters: {
                    'item_group': ['in', ['Raw Material', 'Sub Assemblies']],
                    'is_stock_item': 1
                }
            };
        });

        // Dynamic filters based on doc values
        frm.set_query('warehouse', function() {
            return {
                filters: {
                    'company': frm.doc.company,
                    'is_group': 0
                }
            };
        });
    }
});

Complex Query Filters:

// Pattern from: erpnext/accounts/doctype/payment_entry/payment_entry.js
frappe.ui.form.on('Payment Entry', {
    setup: function(frm) {
        frm.set_query('party', function() {
            let party_type = frm.doc.party_type;
            if (party_type === 'Customer') {
                return {query: 'erpnext.controllers.queries.customer_query'};
            } else if (party_type === 'Supplier') {
                return {query: 'erpnext.controllers.queries.supplier_query'};
            }
        });

        frm.set_query('reference_doctype', 'references', function() {
            let doctypes = [];
            if (frm.doc.party_type === 'Customer') {
                doctypes = ['Sales Invoice', 'Sales Order'];
            } else if (frm.doc.party_type === 'Supplier') {
                doctypes = ['Purchase Invoice', 'Purchase Order'];
            }
            return {
                filters: {
                    'name': ['in', doctypes]
                }
            };
        });
    }
});

10. Dialogs and Prompts

Create Custom Dialog:

// Pattern from: erpnext/stock/doctype/stock_entry/stock_entry.js
frappe.ui.form.on('Stock Entry', {
    get_items: function(frm) {
        let dialog = new frappe.ui.Dialog({
            tit

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Venkateshvenki404224](https://github.com/Venkateshvenki404224)
- **Source:** [Venkateshvenki404224/frappe-apps-manager](https://github.com/Venkateshvenki404224/frappe-apps-manager)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.