Frappe Technologies
Screenshot 2023-10-25 at 10.48.41 PM.png
Class-Based Client Scripts for Frappe CRM
You wanted customization in the new apps, we listened.
author

By

Shariq Ansari

·

22 May 2025

·

3

min read

For years, Frappe apps like ERPNext were built on a desk-based interface powered by the Frappe Framework. It was solid and extensible, but followed a more traditional scripting model that was closely tied to how the framework structured forms and UI interactions.

A few years ago, we started building a new generation of apps like Frappe CRM, with a Vue-based frontend. These brought something we had long wanted: reactivity. Real-time UI updates, a component-driven approach, and a more modern developer experience.

But this new architecture didn’t include the scripting flexibility we had come to rely on in the framework. Features like field-level change triggers, parent-child method calls, and custom logic for dynamic forms were missing.

Rebuilding the sripting layer

The shift to a modern UI wasn’t just about design. It changed how we built features. As Frappe CRM matured, I found myself needing the same scripting power we once took for granted. But now, even simple things like adjusting a field based on user input meant extra workarounds.

These small gaps started adding up. It slowed down development and made the product harder to evolve. Instead of layering on more hacks, it made sense to start fresh with a proper foundation. So I rewrote the scripting layer with a new approach: JavaScript classes that fit naturally into Vue while still offering the control we were used to. The result is a smoother, more powerful way to build dynamic forms.

New in client scripts

Class-based syntax for DocTypes and child tables

Client-side scripting in desk based app's like ERPNext, HRMS etc relies on the older frappe.ui.form.on pattern:

// Old Syntax
frappe.ui.form.on('CRM Deal', {
  category: function (frm) {
    if (frm.doc.category === 'A') {
      frappe.model.set_value(frm.doctype, frm.docname, 'primary_category', 'C');
    } else {
      frappe.model.set_value(frm.doctype, frm.docname, 'primary_category', 'D');
    }
    frm.trigger('primary_category');
  }
});

but Frappe CRM is a Vue based app and it needs a better way.

You can now define scripts using modern ES6 classes for both the main Doctype (CRM Deal) and its child tables (Product Items). Inside each method, you can:

  • Access the document using this.doc
  • Trigger methods with this.doc.trigger('method')
  • Access child table rows using this.doc.getRow('child_fieldname', idx)
// New Syntax
class CRMDeal {
  category() {
    this.doc.primary_category = this.doc.category === 'A' ? 'C' : 'D';
    this.doc.trigger('primary_category');
  }
}

Triggering is now bi-directional (and cleaner)

Interaction Was It Possible Before? New Syntax What’s Better Now
Child to Parent ✅ Yes this.doc.trigger() Now easier and scoped to the main doc
Child to Same Row ✅ Yes row.trigger() Cleaner and more direct
Parent to Child ❌ No this.doc.getRow(...).trigger() Newly supported, adds two-way control

This means you can now trigger child methods from the parent — a big improvement that unlocks more dynamic behavior across the form.

Global helper functions

Several new helper functions are now available globally in your Client Script:

  • createDialog: Create and show a dialog box.
  • toast: Display success or error messages.
  • socket: Trigger real-time events.
  • router: Navigate to different pages using Vue Router.
  • call: Call an API from the client-side.
  • App-specific methods like crm.makePhoneCall are now available too.

Example using createDialog:

category() {
  let me = this
  createDialog({
    title: "Update Category",
    message: "Update category based on primary category?",
    actions: [
      {
        label: "Update",
        variant: "solid",
        onClick(close) {
          me.doc.category = me.doc.primary_category === "A" ? "C" : "D"
          close();
        },
      },
      {
        label: "Cancel",
        variant: "outline",
        onClick(close) {
          close();
        },
      }
    ]
  });
}

Backward compatibility

Worried about breaking changes? Don’t be.

The old Form Script syntax still works, so if you’ve already written custom actions using it, you don’t need to rewrite anything. In fact, you can even mix both styles — use the new class-based syntax for field-level triggers and keep the older one for other logic if that works better for you.

What’s included so far

  • On-field change triggers for Doctypes and their child tables
  • Ability to trigger child row methods from the parent Doctype
  • Clean, bi-directional method triggering between parent and child

What’s coming next

  • Custom actions like buttons and bulk actions will soon be supported in this syntax
  • List actions will also be added in future versions
  • On-refresh triggers are planned to make the class-based experience more complete

If you held off on using Frappe CRM because scripting felt limited, this is a good time to revisit it. With better control and flexibility, customization is back and fits naturally into modern apps. Try it out, and if you build something interesting, share it with us on discuss.frappe.io. We’d love to hear how you're using it.

Published by

Shariq Ansari

on

22 May 2025
1

Share

Add your comment

Success!

Error

Comments

G
Guest

· 

May 27, 2025

Super cool.

Discussion

image7f7d4e.png

Paul Mugambi

·

3 days

ago

Beautiful read, and an insight into an individual I respect and have learned a lot from. Am inspired to trust the process and never give up.

image4c43d6.png

Anna Dane

·

5 days

ago

I must say this is a really amazing post, and for some of my friends who provide Best British Assignment Help, I must recommend this post to them.

Add your comment

Comment