LFForm Custom HTML and Sandbox Integration
This guide explains how to use custom HTML in LFForm safely and effectively, including what works with third-party libraries inside the LFForm sandbox model.
API References
Dive deeper into specific areas of the LFForm API:
-
Read API
Learn how to retrieve field values and find fields.
-
Write API
Discover how to modify fields and handle operations.
-
Events API
Subscribe to and handle form, field, and lookup events.
-
Properties API
Understand runtime form properties and state.
Additional Resources
-
Recipes
Learn more about common patterns and solutions.
-
Custom HTML guide
Check out our custom HTML and sandbox integrations.
Why This Matters
LFForm business logic runs inside a hidden sandbox iframe.
- You cannot directly manipulate the visible form DOM from sandbox script.
- UI libraries that expect direct DOM control can only see the sandbox DOM, which users do not see.
- Logic libraries work well because they do not require visible DOM access.
Custom JavaScript with CORS API Requests
Custom JavaScript code in forms can make cross-origin requests to third-party APIs and external services where CORS is enabled. Scripts run in a security-hardened sandbox on a dedicated domain (sandbox-forms.laserfiche.com), with full network access while remaining isolated from the parent application. You can configure APIs to accept requests only from sandbox-forms.laserfiche.com.
Click here to learn more about CORS support in Laserfiche.
In order for custom JavaScript in a Laserfiche form to make HTTP requests to a third-party API, the target web server must be configured for Cross-Origin Resource Sharing (CORS).
The following example demonstrates fetching data from an external API.
Step 1: Open the Form Designer
- Navigate to the Business Process app in your Laserfiche Cloud.
- Create a new form or open an existing form in the Form Designer.
Step 2: Add Custom JavaScript
- In the Form Designer, click the JavaScript icon:

- Add your custom JavaScript in the Custom JavaScript editor.
The following custom JavaScript example fetches address suggestions from an external API when a user types in a "Street Address" field and populates a dropdown with the results:
// Example: Fetch data from a third-party API with CORS support LFForm.onFieldChange( async function () { const query = LFForm.getFieldValues({ variableName: "Street_Address" }); if (query && query.length > 3) { try { const response = await fetch( "https://api.example.com/address/suggest?q=" + encodeURIComponent(query), { method: "GET", headers: { "Content-Type": "application/json", Authorization: "Bearer YOUR_API_KEY", }, }, ); const data = await response.json(); // Populate a dropdown field with the results const options = data.suggestions.map((s) => s.formattedAddress); LFForm.changeFieldSettings({ variableName: "Address_Suggestions" }, { autoCompleteValues: options }); } catch (error) { console.error("API request failed:", error); } } }, { variableName: "Street_Address" }, );
More Information
CORS must be enabled on the target server. Your JavaScript can make requests to any API that allows cross-origin requests from the sandbox domain. If the target server does not include the appropriate Access-Control-Allow-Origin headers, the request will be blocked by the browser.
Security isolation is enforced. The sandbox prevents custom scripts from accessing the parent application's cookies, localStorage, or navigating to the top-level window. This is by design to protect user sessions.
External scripts load before inline scripts. Any external JavaScript files you configure will be fully loaded before your inline scripts execute, allowing you to reference external library functions.
Support for CORS requests in Laserfiche business process forms is being deployed in phases. If you do not yet see CORS requests working in your forms, contact your administrator to confirm that the feature has been enabled for your account.
Where You Can Render Custom HTML
You can place HTML content in supported LFForm settings:
- CustomHtml field content via content and HTMLContent aliases
- Field text above via description (alias: textAbove)
- Field text below via subtext (alias: textBelow)
Example:
const formFields = {
summaryHtml: { fieldId: 200 },
total: { fieldId: 201 },
};
const renderSummary = async () => {
const total = LFForm.getFieldValues(formFields.total) || 0;
const html = `
<div class="order-summary">
<h3>Order Summary</h3>
<p>Total: <strong>$${total}</strong></p>
</div>
`;
await LFForm.changeFieldSettings(formFields.summaryHtml, { content: html });
};
renderSummary().catch(console.warn);
Third-Party Libraries: What Works and What Does Not
Works well: logic/data libraries
These are safe and useful because they do not depend on controlling visible form DOM.
Important:
- Third-party scripts must be available at runtime.
- Load external scripts and dependencies as external scripts in the Form Designer or use a bundler to bundle them with your form script.
- Imported packages will not work unless they are bundled or otherwise loaded before your LFForm logic runs.
Examples:
- Date formatting libraries (date-fns, moment)
- Google APIs clients
- Validation and parsing libraries
- Data transformation libraries
Use case:
- Calculate and format values
- Call external APIs
- Build HTML strings and write them into CustomHtml, textAbove, or textBelow
Limited value: UI frameworks that require direct DOM control
Libraries such as jQuery can only interact with the sandbox DOM and not the user-visible form DOM.
This means:
- DOM queries and mutations run in the hidden iframe context.
- Visual effects do not appear unless you intentionally copy rendered HTML out to visible CustomHtml/text fields.
- Event handlers attached in sandbox do not automatically become interactive behavior in copied visible HTML.
Pattern: Copy Sandbox HTML into Visible Custom HTML
If you produce markup in sandbox context, treat it as render-to-string output and copy the HTML string into a visible LFForm target.
const formFields = {
widgetHost: { fieldId: 400 },
};
const renderSandboxOutput = async () => {
const sandboxContainer = document.createElement('div');
sandboxContainer.innerHTML = `
<section class="card">
<h4>Status</h4>
<ul>
<li>Ready</li>
<li>Validated</li>
</ul>
</section>
`;
// Copy generated HTML string to a visible CustomHtml field.
await LFForm.changeFieldSettings(formFields.widgetHost, {
content: sandboxContainer.innerHTML,
});
};
renderSandboxOutput().catch(console.warn);
Pattern: Lightweight Actions from CustomHtml Buttons
When you need a simple user action (for example, "Recalculate", "Apply Filter", or "Download CSV"), a button in CustomHtml can call a function in the LFForm sandbox.
Why use this pattern
- It keeps interaction simple and close to the field where users need it.
- It works well for small actions that update field values or trigger utility logic.
- It avoids the overhead of an embedded iframe when full UI isolation is not needed.
Example: button markup rendered in CustomHtml
<button type="button" class="btn btn-primary" onclick="runQuickAction(event, 'expense-summary')"> Refresh Summary </button>
Example: handler defined in LFForm sandbox
window.runQuickAction = async (event, contextKey) => {
if (contextKey !== 'expense-summary') return;
const now = new Date().toISOString();
await LFForm.setFieldValues({ fieldId: 210 }, `Summary refreshed at ${now}`);
// Useful for debugging; event shape is not fully documented.
console.debug('CustomHtml click event', event);
};
Notes:
- Inline handlers resolve global names, so attach callable functions to window.
- The event object is not fully documented, but it usually includes enough details for diagnostics and basic conditional logic.
- Keep this pattern focused on lightweight actions; use postMessage and hosted iframes for richer interactive interfaces.
Pattern: Hosted External UI in an Iframe
For rich interactive UI (payments, maps, custom widgets), host your own page and embed it in a CustomHtml field using an iframe.
This is a common approach for payment and complex widget integrations.
1. Inject iframe markup into CustomHtml field
const formFields = {
paymentHost: { fieldId: 500 },
};
const mountPaymentIframe = async () => {
const iframeHtml = `
<iframe
id="stripe-frame"
title="Secure Payment"
src="https://your-domain.example/stripe-checkout.html"
style="width:100%;min-height:680px;border:0"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
></iframe>
`;
await LFForm.changeFieldSettings(formFields.paymentHost, { content: iframeHtml });
};
mountPaymentIframe().catch(console.warn);
2. Bridge data with postMessage
Use window message handlers in LFForm script to receive updates from the hosted iframe page.
Known sandbox origin:
- LFForm sandbox is hosted on https://sandbox-forms.laserfiche.com.
- API and integration services can add this origin to CORS and origin allow-lists.
- For cross-frame messaging, explicitly allow this origin in postMessage origin checks.
LFForm sandbox frame example (receive from hosted iframe, then send an ack back):
const formFields = {
paymentToken: { fieldId: 501 },
paymentStatus: { fieldId: 502 },
};
const allowedOrigin = 'https://your-domain.example';
window.addEventListener('message', async (event) => {
if (event.origin !== allowedOrigin) return;
const data = event.data || {};
if (data.type === 'payment-token') {
await LFForm.setFieldValues(formFields.paymentToken, data.token || '');
await LFForm.setFieldValues(formFields.paymentStatus, 'Tokenized');
// Reply to the sender frame.
event.source?.postMessage({ type: 'payment-ack', ok: true }, allowedOrigin);
}
if (data.type === 'payment-error') {
await LFForm.setFieldValues(formFields.paymentStatus, 'Failed');
}
});
Hosted iframe page example (send to LFForm sandbox, then receive ack):
const allowedOrigin = 'https://sandbox-forms.laserfiche.com';
const sendPaymentToken = (token) => {
window.parent.postMessage({ type: 'payment-token', token }, allowedOrigin);
};
window.addEventListener('message', (event) => {
if (event.origin !== allowedOrigin) return;
const data = event.data || {};
if (data.type === 'payment-ack' && data.ok) {
console.log('Sandbox acknowledged token');
}
});
Recommended Architecture
For reliable production behavior:
- Use LFForm APIs as the source of truth for form state.
- Use third-party logic libraries for computation, formatting, and API calls.
- Render visible HTML through LFForm field settings (content, description, subtext).
- For rich interactive UI, host a standalone page and embed via iframe.
- Exchange data through postMessage with strict origin checks.
Security and Reliability Checklist
- Validate postMessage origin before using message data.
- Add https://sandbox-forms.laserfiche.com to trusted origin/CORS allow-lists when integrating external APIs.
- Never trust iframe payloads without validation.
- Keep API keys and secrets server-side.
- Avoid inline script injection in generated HTML.
- Gracefully handle readonly, print, and disabled contexts.
- Always await LFForm mutating methods.
Common Pitfalls
- Assuming jQuery selectors can target user-visible LFForm DOM directly.
- Expecting copied HTML to preserve sandbox-attached event handlers.
- Forgetting that repeatable fields require fieldId plus index for row-specific targeting.
- Writing large HTML strings repeatedly without throttling updates.