Skip to content
Skip to the article
In Frappe: 20 articles
Frappe

Quick Entry Forms

Build custom Quick Entry dialogs that create several linked documents from a single form.

Updated
Tags
  • frappe
  • javascript
  • dialog
  • client-side
Reading time
3 min

Custom Quick Entry dialogs provide efficient data entry that can create multiple linked documents in a single form submission.

Basic Pattern

Extend frappe.ui.form.QuickEntryForm to add custom fields and behavior:

frappe.ui.form.MyQuickEntryForm = class MyQuickEntryForm extends (
    frappe.ui.form.QuickEntryForm
) {
    constructor(doctype, after_insert, init_callback, doc, force) {
        super(doctype, after_insert, init_callback, doc, force);
        this.skip_redirect_on_error = true;  // Keep dialog open on failure
    }

    render_dialog() {
        // Add custom fields before rendering
        this.mandatory = this.mandatory.concat(this.get_variant_fields());
        super.render_dialog();
    }

    get_variant_fields() {
        return [
            // Custom fields added to the quick entry dialog
        ];
    }
};

Registration

Wire the custom form to a doctype's list view via frappe.listview_settings:

frappe.listview_settings["Player"] = {
    hide_name_column: true,
    hide_name_filter: true,
    quick_entry: true,
    quick_entry_doc: frappe.ui.form.PlayerQuickEntryForm,
};

File location: <doctype_name>_list.js alongside the DocType definition. Frappe automatically loads these for list views.

Adding Custom Fields

The get_variant_fields() method returns field definitions using the same schema as DocType fields:

get_variant_fields() {
    return [
        {
            label: __("Create New Family"),
            fieldname: "create_new_family",
            fieldtype: "Check",
        },
        {
            fieldtype: "Section Break",
            label: __("Family Details"),
            depends_on: "eval:doc.create_new_family",
        },
        {
            label: __("Family Name"),
            fieldname: "family_name",
            fieldtype: "Data",
            mandatory_depends_on: "eval:doc.create_new_family",
        },
        {
            fieldtype: "Section Break",
            label: __("Existing Family"),
            depends_on: "eval:!doc.create_new_family",
        },
        {
            label: __("Family"),
            fieldname: "family",
            fieldtype: "Link",
            options: "Family",
            mandatory_depends_on: "eval:!doc.create_new_family",
        },
        {
            fieldtype: "Section Break",
            label: __("Guardian Contact"),
            depends_on: "eval:doc.create_new_family",
        },
        {
            label: __("Email"),
            fieldname: "email_address",
            fieldtype: "Data",
            options: "Email",
        },
        {
            label: __("Phone"),
            fieldname: "phone",
            fieldtype: "Data",
            options: "Phone",
        },
    ];
}

Key field properties:

  • depends_on — Conditional visibility using eval: expressions

  • mandatory_depends_on — Conditional mandatory requirement

  • options: "Email" / options: "Phone" — Built-in validation for Data fields

Multi-Document Creation

Override insert() to create linked documents before the main document:

async insert() {
    if (this.dialog.doc.create_new_family) {
        await this.create_family();
    }
    return super.insert();
}

async create_family() {
    const doc = this.dialog.doc;

    // 1. Create the Family
    const family = await frappe.db.insert({
        doctype: "Family",
        family_name: doc.family_name,
    });

    // 2. Create a Contact for the guardian
    const contact = await frappe.db.insert({
        doctype: "Contact",
        first_name: doc.first_name,
        last_name: doc.last_name,
        email_ids: [{ email_id: doc.email_address, is_primary: 1 }],
        phone_nos: [{ phone: doc.phone, is_primary_mobile_no: 1 }],
    });

    // 3. Add guardian to the family
    await frappe.db.insert({
        doctype: "Family Guardian",
        parent: family.name,
        parenttype: "Family",
        parentfield: "guardians",
        guardian: contact.name,
    });

    // 4. Set the family on the main document
    this.dialog.doc.family = family.name;

    // 5. Clean up temporary fields
    delete this.dialog.doc.family_name;
    delete this.dialog.doc.email_address;
    delete this.dialog.doc.phone;
    delete this.dialog.doc.create_new_family;
}

Alias Field Mapping

When the target doctype has readonly fields that would be hidden in dialogs, use alias fieldnames and remap them before insertion:

insert() {
    const map_field_names = {
        email_address: "email_id",       // email_id is readonly on Contact
        mobile_number: "mobile_no",      // mobile_no is readonly on Contact
        map_to_first_name: "first_name",
        map_to_last_name: "last_name",
    };

    Object.entries(map_field_names).forEach(([alias, real]) => {
        this.dialog.doc[real] = this.dialog.doc[alias];
        delete this.dialog.doc[alias];
    });

    return super.insert();
}

This lets users fill in email_address (which is visible and editable) instead of email_id (which Frappe hides because it's readonly).

Combining Address and Contact Entry

A common pattern merges two linked doctypes into one quick entry:

get_variant_fields() {
    return [
        // Contact fields
        { fieldtype: "Section Break", label: __("Contact Details") },
        { label: __("First Name"), fieldname: "map_to_first_name", fieldtype: "Data" },
        { label: __("Last Name"), fieldname: "map_to_last_name", fieldtype: "Data" },
        { label: __("Email"), fieldname: "email_address", fieldtype: "Data", options: "Email" },
        { label: __("Phone"), fieldname: "mobile_number", fieldtype: "Data", options: "Phone" },

        // Address fields
        { fieldtype: "Section Break", label: __("Address Details") },
        { label: __("Address Line 1"), fieldname: "address_line1", fieldtype: "Data" },
        { label: __("City"), fieldname: "city", fieldtype: "Data" },
        { label: __("State"), fieldname: "state", fieldtype: "Data" },
        { label: __("Country"), fieldname: "country", fieldtype: "Link", options: "Country" },
        { label: __("ZIP Code"), fieldname: "pincode", fieldtype: "Data" },
    ];
}

Key Design Decisions

DecisionApproach
Keep dialog open on errorthis.skip_redirect_on_error = true in constructor
Add fields before renderingModify this.mandatory in render_dialog() before super.render_dialog()
Conditional field visibilityUse depends_on: "eval:doc.field_name"
Create linked docsOverride insert(), create via frappe.db.insert(), then call super.insert()
Handle readonly target fieldsUse alias fieldnames + remap in insert()
Clean up temp fieldsdelete this.dialog.doc.temp_field before super.insert()

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.