Skip to content

Web Form Development Patterns

Patterns for building Frappe Web Forms (portal pages) — client scripts, CSS, and gotchas.

Client Script Patterns

Basic Structure

javascript
frappe.ready(function() {
    // All Web Form code goes inside frappe.ready()
    // This ensures the form is fully loaded before running

    // Set initial values
    frappe.web_form.set_values({
        date: frappe.datetime.get_today(),
        status: 'Open'
    });

    // Listen for field changes
    frappe.web_form.on('change', function(field, value) {
        if (field === 'category') {
            handle_category_change(value);
        }
    });
});

Set Read-Only Fields

javascript
frappe.ready(function() {
    // Read-only fields CAN still be set via set_values()
    frappe.web_form.set_values({
        employee_name: frappe.session.user_fullname,
        submit_date: frappe.datetime.get_today()
    });
});

Conditional Field Visibility

javascript
frappe.ready(function() {
    frappe.web_form.on('change', function(field, value) {
        if (field === 'request_type') {
            // Show/hide fields based on selection
            let show_urgency = (value === 'Emergency');
            frappe.web_form.fields_dict.urgency_level.$wrapper.toggle(show_urgency);
            frappe.web_form.fields_dict.emergency_contact.$wrapper.toggle(show_urgency);
        }
    });
});

Custom Validation Before Submit

javascript
frappe.ready(function() {
    frappe.web_form.validate = function() {
        let values = frappe.web_form.get_values();

        if (!values.email || !values.email.includes('@')) {
            frappe.msgprint(__('Please enter a valid email address'));
            return false; // Prevents submission
        }

        if (values.start_date > values.end_date) {
            frappe.msgprint(__('End date must be after start date'));
            return false;
        }

        return true; // Allow submission
    };
});

Call Server Methods

javascript
frappe.ready(function() {
    frappe.web_form.on('change', function(field, value) {
        if (field === 'employee_id') {
            frappe.call({
                method: 'my_app.api.get_employee_details',
                args: { employee_id: value },
                callback: function(r) {
                    if (r.message) {
                        frappe.web_form.set_values({
                            employee_name: r.message.employee_name,
                            department: r.message.department
                        });
                    }
                }
            });
        }
    });
});

After Load / After Save

javascript
frappe.ready(function() {
    // After form loads
    frappe.web_form.after_load = function() {
        console.log('Form loaded with data:', frappe.web_form.doc);
    };

    // After form saves
    frappe.web_form.after_save = function() {
        frappe.msgprint(__('Thank you! Your submission has been recorded.'));
    };
});

Common Gotchas

1. Timing Issues

javascript
// ❌ WRONG: Code runs before form is ready
frappe.web_form.set_values({ field: 'value' });

// ✅ CORRECT: Always wrap in frappe.ready()
frappe.ready(function() {
    frappe.web_form.set_values({ field: 'value' });
});

2. set_value vs set_values

javascript
// ❌ WRONG: set_value doesn't exist on WebForm
frappe.web_form.set_value('field', 'value');

// ✅ CORRECT: Use set_values (plural) with object
frappe.web_form.set_values({ field: 'value' });

3. Guest Access

javascript
// For forms allowing guest submissions, check login state:
frappe.ready(function() {
    if (frappe.session.user === 'Guest') {
        // Show limited fields
        frappe.web_form.fields_dict.internal_field.$wrapper.hide();
    }
});

4. File Uploads in Web Forms

javascript
// Web Forms handle file uploads differently
// Use the Attach field type in DocType, Web Form will auto-create upload widget
// For custom upload handling:
frappe.ready(function() {
    frappe.web_form.on('change', function(field, value) {
        if (field === 'attachment' && value) {
            console.log('File uploaded:', value);
        }
    });
});