A toolkit for building Laserfiche Forms customizations with TypeScript, Vite, and reusable components.
Important
This toolkit is not officially supported by Laserfiche and may break with future updates to Laserfiche Forms. Do not open support tickets with Laserfiche for issues related to this toolkit.
This is a monorepo with npm workspaces:
lf-form-builder/
├── docs/ # VitePress guides, recipes, and generated API docs
├── packages/
│ ├── core/ # Local workspace package (@lf/lf-form-builder)
│ │ └── src/ # Library source: components, utilities, API helpers, plugins
│ ├── types/ # Local workspace package (@lf/lf-form-types)
│ │ └── src/ # LFForm type definitions and helpers
│ └── examples/ # Real-world example forms
├── template/ # Starter template workspace — copy this to start a new project
@lf/lf-form-builder and @lf/lf-form-types are not published to any npm registry. They are
built from source in this repository and consumed through npm workspaces, so every dependent —
packages/examples and template — resolves them from packages/ on disk. Their package.json
entries use "*", which npm satisfies with the local workspace copy rather than a registry lookup.
The template is a workspace in this repository, so a single install at the repo root wires it to the locally built library:
git clone https://github.com/Laserfiche/lf-form-builder.git
cd lf-form-builder
npm install # links @lf/lf-form-builder and @lf/lf-form-types from packages/
npm run build:core # build the library the template imports
npm run dev:template # build + serve the template on http://localhost:3000To start your own project, copy template/ to a new folder inside this repository and add it to
the workspaces array in the root package.json, then re-run npm install:
cp -r template my-forms-project::: tip Working outside this repository
Because the library is never published, a project kept outside this checkout cannot resolve
@lf/lf-form-builder by name. If you need a standalone copy, build and pack the two packages first,
then depend on the tarballs:
npm run build:core
npm pack --workspace=packages/types --workspace=packages/core --pack-destination ../// my-forms-project/package.json
"dependencies": {
"@lf/lf-form-builder": "file:../lf-lf-form-builder-0.1.0.tgz",
"@lf/lf-form-types": "file:../lf-lf-form-types-0.1.0.tgz"
}Re-pack and re-install after each library change. :::
See template/README.md for full documentation.
git clone https://github.com/Laserfiche/lf-form-builder.git
cd lf-form-builder
npm install
copy .env.example .env.local
npm run build:core # Build the core library
npm run build:examples # Build example forms
npm run build:dev:css # Dev build + compile emitted .less to .css for static serving
npm run dev # Watch examples and serve built dist locallyUse .env.example as the committed template for local configuration. Keep actual keys and per-machine overrides in the untracked .env.local.
After changing .env.local, rebuild the examples bundle or restart npm run dev. The VITE_* values are embedded into the generated JavaScript at build time, so updating the env file alone does not change an already-built packages/examples/dist/*.js output.
If you use the translation examples, set VITE_TRANSLATION_ENDPOINT to a server-side route that performs translation. Do not expose provider API keys in browser code.
For live rebuilds when editing the library or examples, use the repository watch script. It rebuilds core, examples, and re-compiles emitted .less files automatically on save:
npm install # first-time only
npm run watch # rebuilds on file changesUse the workflow that matches what you are testing:
npm run dev— runs the examples workspacewatch+servescripts. This watches the example build output and servespackages/examples/distonhttp://localhost:3000when that port is available.npm run build:dev:css— one-shot development build that emits bothEmpower2026.jsandEmpower2026.cssintopackages/examples/dist.npm run watch-serve— runs the core watcher and a Vite development server onhttp://localhost:3000with--strictPort. Use this when you want Vite-hosted development output on a fixed port.
If you want the built dist directory served manually from the examples workspace:
# build once (dev mode) and emit matching .css files into dist
npm run build:dev:css
# serve the built dist on port 3000
npm --workspace=packages/examples run serveNotes:
npm run devuses the examples package's staticservecommand. If port3000is already in use,servewill choose another port.npm run build:dev:cssis the easiest one-shot build when you want the static index page to list bothEmpower2026.jsandEmpower2026.css.npm run watch-serveuses Vite on--strictPort, so it will fail instead of switching ports if3000is already occupied.- If you update
.env.local, run a fresh build or restartnpm run devso the newVITE_*values are embedded into the built JavaScript. - If
Empower2026.jslooks truncated, stop the server, run thebuild:devorbuild:dev:cssstep to completion, then restart serving.
This is useful during development so you don't need to run npm run build:core manually after each change.
- Quick Start — Writing form scripts with the LFForm API
- LFForm API Navigation — Browse the main LFForm API pages in a linear order, then drill into auxiliary types
- Template & Toolchain Setup — Build with Vite and the local workspace packages
- Custom HTML & Sandbox — Custom HTML, third-party libraries, and iframes
- Recipes — Copy-paste patterns for common form tasks
- Payment Gateways — Setting up Stripe, Braintree, or Authorize.net checkout on a form
- API Reference — Generated TypeDoc for
@lf/lf-form-builderand@lf/lf-form-types
The core library published to npm. Import utilities in your form code:
import { findField, LFFormModal, fullFieldHtml } from '@lf/lf-form-builder';Import Vite plugins in your build config:
import { bundleLfless, disableSharedChunking } from '@lf/lf-form-builder/plugins';Included modules:
- Field utilities —
findField,findFieldByIdParam,findFieldOrNull - Field rules —
LFFormFieldRules(chainable show/hide/CSS class actions) - Safe field helpers —
showFieldSafe,hideFieldSafe,setFieldValueSafe - Templates —
lfjsx(reactive field content templates) - Async and general utilities —
waitWithTimeout,throttle,setCustomHtml - Messaging utilities —
PostMessageHelperand related message types - Table utilities —
fillTableWithGenericResults,setTableFieldValues,generateCSV,makeCSVDownloadButton,makeDownloadTriggerButton,updateTableRows - API helpers —
getRepositories,resolveEntryIdField,resolveDefaultRepositoryAPIOptions,searchAsync,fillTableWithSearchResults,defaultSearchOptions,mapEntryToForm,isFieldIdEqualField,patchEntryMetadata,AdobeSignApi - Components —
LFFormModal,generateFullFieldHtml,fullFieldHtml,fieldFormatter,registerStarHandler,registerAllStarComponents,handleVoteChange,onVoteChange,makeLoadingBar,registerFirstTimeLoad - Google Maps —
initGoogleMapsAutocomplete,destroyGoogleMapsAutocomplete,buildLFAddress,initAddressValidation,destroyAddressValidation,sendValidationFeedback,validateAddress,validateFeedback - Repository —
DocView,IframeView - Vite plugins —
bundleLfless,disableSharedChunking,generateDirectoryHtml - CSS — LESS variables, mixins, and theme files via
@lf/lf-form-builder/css/*
See packages/core/README.md for package-level usage and plugin details.
TypeScript definitions for the LFForm runtime API, including focused subpath exports for the main LFForm API and auxiliary helper types.
import type { LFForm, LFFormEventApi, LFFormGetterApi, LFFormMethodApi } from '@lf/lf-form-types/lfform';
import type { LFFormSupportedEvents } from '@lf/lf-form-types/events';
import { isLfFormId } from '@lf/lf-form-types/utils';See packages/types/README.md and docs/guide/lfform-api-navigation.md for the LFForm-first docs structure.
A minimal starter project configured with Vite, @lf/lf-form-builder, and @lf/lf-form-types. Copy this directory to start a new forms project.
Real-world form implementations demonstrating the library in action:
- Empower2025 — Multi-feature form: modals, star ratings, table HTML injection
- Empower2026 — lookup-driven loading and CSV export, Google Maps autocomplete with address validation, modal-based checkout via Stripe, Braintree, or Authorize.net, cross-iframe messaging
- TranslateForm — static form localization with optional translation draft generation
Empower2026 ships three interchangeable payment-gateway pages, each independently toggleable via a VITE_DISABLE_PAGE* flag in .env.local:
| Page | File | Gateway | Feature flag |
|---|---|---|---|
| Page 3 | page3Payment.ts | Stripe (embedded checkout) | VITE_DISABLE_PAGE3 |
| Page 4 | page4BraintreePayment.ts | Braintree (Drop-in UI) | VITE_DISABLE_PAGE4 |
| Page 5 | page5AuthorizeNetPayment.ts | Authorize.net (direct tokenization) | VITE_DISABLE_PAGE5 |
Warning
Accepting card payments puts you in scope for PCI DSS. These are unsupported reference samples, not a certified payment application, and the three do not carry the same PCI scope as each other — page 5 renders card fields in markup this repository controls, pages 3 and 4 do not. Read PCI DSS scope and your SAQ before adapting any of them. Never add card number, expiry, or CVV fields to a form design. Your acquirer and QSA determine which SAQ applies to you.
See docs/recipes/payment-gateways.md for the full setup and field-mapping guide for all three gateways.
Before using the Empower2026 example in your own Forms account, import the bundled Laserfiche assets from packages/examples/src/Forms/Empower2026/Laserfiche Process:
- Import the process definition: AdvancedScripting_RG.xml
- For Stripe (page 3), import the web request rules: RGWebRequestToStripeCreateSession.bri (creates the Checkout Session and returns its
client_secret— it does not charge the card) and RGWebRequestToStripeVerification.bri - For Braintree (page 4), import: RGWebRequestToBraintreePaymentIInit.bri (creates the client_token) and RGWebRequestToBraintreeCharge.bri (charges the nonce)
- For Authorize.net (page 5), import: RGWebRequestToAuthNetCharge.bri and RGWebRequestToAuthNetVerification.bri
You only need to import the web request rules for the gateway(s) you intend to use; disable the others via their VITE_DISABLE_PAGE* flag.
Those imports are required for the example's process actions and gateway-related web requests to work in your environment.
The bundled process assets are configured for local development and expect the example scripts to be served from http://localhost:3000. That is appropriate for getting the demo running quickly on a developer machine.
For the payment gateways specifically, do not copy only Empower2026.js. Each checkout flow injects an iframe pointed back at the currently-loaded page (with the built script re-delivered into that iframe over postMessage), so Empower2026.js must be reachable at the same URL the form iframe loads — there is no separate stripe.html/Stripe.js asset to copy alongside it. In local development that URL is served from packages/examples/dist, and in production it should be hosted on the same HTTPS location referenced by the form.
The page the payment iframe actually loads is the Forms renderer's own sandbox.html — form scripts already execute inside it, so window.location.pathname resolves to it. It is not an asset you host. See The sandbox bootstrap.
For production, do not leave the process pointed at localhost. Host the built JavaScript, CSS, and related assets on a secure HTTPS site that your Forms environment can reach, then update the imported process and form configuration to reference that hosted location.
There is no publish step — the packages are consumed locally. To confirm the whole workspace is healthy after a change:
npm run build:types
npm run build:core
npm run build:examples
npm run build:template
npm run lint
npm run typecheck
npm testVersions in packages/core/package.json and packages/types/package.json are informational only.
Nothing resolves them by range, so bumping them is optional and requires no dependent updates.