Custom JavaScript
Add custom logic to GenZform forms using JavaScript hooks for calculations, conditional navigation, real-time validation, and form submission handling.
GenZform forms support custom JavaScript through the on_next_page_scripts feature. This allows you to add calculations, conditional navigation, real-time field validation, and custom submission handling directly in your form.
Custom JavaScript is an advanced feature for forms that need dynamic calculations or complex logic. Most forms don't need this—use the built-in field types and conditional visibility for standard use cases.
Available Hooks
GenZform provides three JavaScript hooks you can define in your form's on_next_page_scripts:
| Hook | When It's Called | Return Value |
|---|---|---|
window.on_next_button_click | User clicks "Next" button | { can_proceed, custom_variables, page_no } |
window.on_form_submit | User submits the form | Final data object to submit |
window.on_input_blur | User leaves any input field | { custom_variables, formData } |
on_next_button_click
Called when the user clicks the "Next" button to move between pages. Use this for:
- Page-specific calculations
- Custom validation
- Conditional page navigation
- Storing calculated values
window.on_next_button_click = function(formData, custom_variables, current_page_index) {
// Example: Calculate total on page 1
if (current_page_index === 0) {
const quantity = parseFloat(formData.quantity) || 0;
const price = parseFloat(formData.unit_price) || 0;
const subtotal = quantity * price;
const tax = subtotal * 0.1;
// Store in custom_variables for use in later pages
custom_variables.subtotal = subtotal.toFixed(2);
custom_variables.tax = tax.toFixed(2);
custom_variables.total = (subtotal + tax).toFixed(2);
}
return {
can_proceed: true, // Allow/block navigation
custom_variables: custom_variables, // Updated variables
page_no: current_page_index + 1 // Which page to go to
};
};Parameters
| Parameter | Type | Description |
|---|---|---|
formData | Object | Current form field values keyed by field ID |
custom_variables | Object | Persistent variables you can store between pages |
current_page_index | Number | Current page index (0-based) |
Return Value
| Property | Type | Description |
|---|---|---|
can_proceed | Boolean | If false, navigation is blocked |
custom_variables | Object | Updated custom variables to persist |
page_no | Number | Page index to navigate to |
Conditional Navigation
Skip pages based on user input:
window.on_next_button_click = function(formData, custom_variables, current_page_index) {
// Skip page 2 (index 1) if user selected "No" for detailed info
if (current_page_index === 0 && formData.want_details === 'no') {
return {
can_proceed: true,
custom_variables: custom_variables,
page_no: 2 // Skip to page 3 (index 2)
};
}
return {
can_proceed: true,
custom_variables: custom_variables,
page_no: current_page_index + 1
};
};Validation with Error Messages
Block navigation and show an error:
window.on_next_button_click = function(formData, custom_variables, current_page_index) {
if (current_page_index === 0) {
const email = formData.email || '';
// Validate email domain
if (!email.endsWith('@company.com')) {
alert('Please use your company email address');
return {
can_proceed: false,
custom_variables: custom_variables,
page_no: current_page_index
};
}
}
return {
can_proceed: true,
custom_variables: custom_variables,
page_no: current_page_index + 1
};
};on_form_submit
Called when the user submits the final page. Use this for:
- Final calculations before submission
- Data transformation
- Adding computed fields to the submission
window.on_form_submit = function(formData, custom_variables, current_page_index) {
// Add calculated totals to submission data
return {
...formData,
calculated_total: custom_variables.total,
calculated_tax: custom_variables.tax,
submission_timestamp: new Date().toISOString()
};
};Parameters
| Parameter | Type | Description |
|---|---|---|
formData | Object | All form field values |
custom_variables | Object | Custom variables from previous pages |
current_page_index | Number | Final page index |
Return Value
Return the final data object to be submitted. This object will be saved as the form response.
on_input_blur
Called when any input field loses focus (user clicks/tabs away). Use this for:
- Real-time calculations as the user types
- Live total updates
- Field-level validation feedback
- Dependent field updates
window.on_input_blur = function(formData, custom_variables, current_page_index, field_id) {
// Update running total whenever quantity or price changes
if (field_id === 'quantity' || field_id === 'unit_price') {
const quantity = parseFloat(formData.quantity) || 0;
const price = parseFloat(formData.unit_price) || 0;
custom_variables.running_total = (quantity * price).toFixed(2);
}
return {
custom_variables: custom_variables,
formData: formData
};
};Parameters
| Parameter | Type | Description |
|---|---|---|
formData | Object | Current form field values |
custom_variables | Object | Custom variables |
current_page_index | Number | Current page index |
field_id | String | ID of the field that lost focus |
Return Value
| Property | Type | Description |
|---|---|---|
custom_variables | Object | Updated custom variables |
formData | Object | Updated form data (for field modifications) |
Using Custom Variables
Custom variables are persistent across pages and can be displayed in your form using macros.
Storing Values
// In on_next_button_click or on_input_blur
custom_variables.monthly_payment = payment.toFixed(2);
custom_variables.loan_term_years = 30;
custom_variables.interest_rate = '6.5%';Displaying Values
Use the {{variable_name}} macro syntax in any text field, label, or content box:
Your estimated monthly payment is ${{monthly_payment}}Dynamic Content
Use JavaScript expressions in macros:
{{total > 1000 ? 'Large order discount applied!' : 'Add more items for a discount'}}Best Practices
Always Validate Numeric Inputs
// Good: Handles missing or invalid values
const value = parseFloat(formData.amount) || 0;
// Bad: Will fail if amount is empty or non-numeric
const value = formData.amount;Handle Edge Cases
// Check for division by zero
if (denominator !== 0) {
result = numerator / denominator;
} else {
result = 0;
}
// Check for negative values when they don't make sense
const quantity = Math.max(0, parseFloat(formData.quantity) || 0);Format Currency Values
// Use toFixed for consistent decimal places
custom_variables.total = total.toFixed(2); // "123.45"
// For display with currency symbol, use in the form label:
// "Total: ${{total}}"Use Descriptive Variable Names
// Good: Clear what each variable represents
custom_variables.monthly_loan_payment = payment;
custom_variables.total_interest_paid = totalInterest;
// Bad: Unclear abbreviations
custom_variables.mlp = payment;
custom_variables.tip = totalInterest;Example: Loan Calculator
A complete example showing all three hooks working together:
// Real-time updates as user fills in values
window.on_input_blur = function(formData, custom_variables, current_page_index, field_id) {
if (current_page_index === 0) {
const principal = parseFloat(formData.loan_amount) || 0;
const rate = parseFloat(formData.interest_rate) || 0;
const years = parseFloat(formData.loan_term) || 1;
if (principal > 0 && rate > 0) {
const monthlyRate = rate / 100 / 12;
const months = years * 12;
const payment = principal * (monthlyRate * Math.pow(1 + monthlyRate, months))
/ (Math.pow(1 + monthlyRate, months) - 1);
custom_variables.estimated_payment = payment.toFixed(2);
}
}
return { custom_variables, formData };
};
// Finalize calculation when moving to results page
window.on_next_button_click = function(formData, custom_variables, current_page_index) {
if (current_page_index === 0) {
const principal = parseFloat(formData.loan_amount) || 0;
const rate = parseFloat(formData.interest_rate) || 0;
const years = parseFloat(formData.loan_term) || 1;
// Validation
if (principal <= 0) {
alert('Please enter a valid loan amount');
return { can_proceed: false, custom_variables, page_no: 0 };
}
const monthlyRate = rate / 100 / 12;
const months = years * 12;
const payment = principal * (monthlyRate * Math.pow(1 + monthlyRate, months))
/ (Math.pow(1 + monthlyRate, months) - 1);
const totalPaid = payment * months;
const totalInterest = totalPaid - principal;
custom_variables.monthly_payment = payment.toFixed(2);
custom_variables.total_paid = totalPaid.toFixed(2);
custom_variables.total_interest = totalInterest.toFixed(2);
}
return {
can_proceed: true,
custom_variables,
page_no: current_page_index + 1
};
};
// Add summary to submission
window.on_form_submit = function(formData, custom_variables, current_page_index) {
return {
...formData,
calculated_monthly_payment: custom_variables.monthly_payment,
calculated_total_interest: custom_variables.total_interest
};
};Reserved Variables
These variables are reserved by GenZform and should not be overwritten:
| Variable | Description |
|---|---|
{{score}} | Quiz score (total points earned) |
{{percentage}} | Quiz percentage (0-100) |
{{max_score}} | Maximum possible quiz score |
Frequently Asked Questions
Where do I add custom JavaScript?
Custom JavaScript goes in the on_next_page_scripts field of your form's JSON configuration. If you're using the AI chat to build forms, describe the calculation logic you need and the AI will generate the appropriate scripts.
Can I use external libraries?
No. The scripts run in a sandboxed environment without access to external libraries. Use vanilla JavaScript for all calculations.
How do I debug my scripts?
Use console.log() statements to debug. Open your browser's developer tools (F12) and check the Console tab while testing your form.
Why isn't my calculation working?
Common issues: (1) Not parsing string values to numbers with parseFloat(), (2) Missing fallback values (|| 0), (3) Incorrect page index in conditional logic, (4) Typos in field IDs.