This guide helps you write reliable form scripts quickly with strong TypeScript hints and production-safe JavaScript patterns.
- A form running the LFForm runtime (not classic designer)
- Script logic running in form business logic
Use one async entry point and await LFForm state-changing calls.
const formFields = {
firstName: { fieldId: 10 },
lastName: { fieldId: 11 },
fullName: { fieldId: 12 },
};
const setFullName = async () => {
const first = LFForm.getFieldValues(formFields.firstName);
const last = LFForm.getFieldValues(formFields.lastName);
if (!first || !last) return;
await LFForm.setFieldValues(formFields.fullName, `${last}, ${first}`);
};
LFForm.onFieldChange(setFullName, formFields.firstName);
LFForm.onFieldChange(setFullName, formFields.lastName);
const main = async () => {
if (LFForm.isReadonly || LFForm.isPrint || LFForm.isDisabled) return;
await setFullName();
};
main().catch(console.warn);fieldId is the most reliable identifier, especially for table and collection fields.
Do not rely on variableName for table and collection fields.
Use index with fieldId for specific rows in repeatable contexts.
const formFields = {
customerName: { fieldId: 100 },
invoiceTotalColumn: { fieldId: 200 },
};
const allRowTotals = LFForm.getFieldValues(formFields.invoiceTotalColumn);
const firstRowTotal = LFForm.getFieldValues({ fieldId: 200, index: 0 });Use one or more of these keys to target fields:
fieldId?: numbervariableName?: stringvariableId?: stringtrackId?: stringindex?: number
index starts at 0 for table/collection rows.
const formFields = {
email: { fieldId: 300 },
status: { fieldId: 301 },
choices: { fieldId: 302 },
};
const email = LFForm.getFieldValues(formFields.email);
await LFForm.setFieldValues(formFields.status, 'Ready');
await LFForm.setFieldValues(formFields.choices, { value: ['A', 'B'] });const formFields = {
email: { fieldId: 300 },
comments: { fieldId: 400 },
};
LFForm.onFieldChange(() => console.log('changed'), formFields.email);
LFForm.onFieldBlur(() => console.log('blurred'), formFields.email);
LFForm.onFormSubmission((event) => {
const action = event?.data?.action?.value;
if (action === 'Reject' && !LFForm.getFieldValues(formFields.comments)) {
return { error: 'Comments are required for Reject.' };
}
});LFForm.onFormSubmission(async function (event) {
// Get the value of the clicked submission button
const userAction = event.data.action.value;
const approvalComments = LFForm.getFieldValues({ fieldId: 2});
if (userAction === "Reject" && approvalComments === "") {
await LFForm.showFields({ fieldId: 2 });
return { error: "Please add comments in order to approve." };
} else {
await LFForm.hideFields({ fieldId: 2 });
}
});- Not awaiting mutating methods:
setFieldValues,showFields,hideFields,addRow, etc. - Writing to fields in readonly/print contexts
- Assuming table/collection calls always return a scalar (they may return arrays)
- Omitting
indexwhen targeting a specific row
- LFForm API Navigation - Fast route to Events, Getters, Methods, and helper type references
- Template & Toolchain Setup — Build form scripts with Vite and the npm packages
- Custom HTML & Sandbox — Using custom HTML, third-party libraries, and iframes
- Recipes — Copy-paste patterns for common form tasks
- API Reference — Full generated API documentation