form-js gives a BPMN tool three ways to view, edit and simulate a JSON form
View and visually edit JSON-based forms.
At a glance
- What is it?
- form-js is a JavaScript library from the bpmn.io project that renders, edits and simulates forms described by a JSON schema, split into a viewer, an editor and a playground package. The interesting part is that the schema is data, so a form can be created in a browser and then inspected programmatically for the variables it consumes and produces.
- Who is it for?
- Adopt form-js if your application needs forms that a user can fill in and that a developer can also lay out visually, because the JSON schema is the single source of truth for both, and the editor package gives you the visual editing surface that a hand-written form definition never has.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three packages, one schema
form-js comes from bpmn.io, the project behind the BPMN modelling toolkit, and the repository describes itself as the place to view, visually edit and simulate JSON-based forms.
The package split is the architecture. There is a viewer, which renders a form from a schema and existing data. There is an editor, which lets someone create or change a form visually. And there is a playground, which creates a form and simulates it with input and output data. All three are exported from the top-level package, and each also has its own directory under packages/ with its own documentation and its own npm scope name.
The reason to build all three from one schema is the point of the project. A form defined as JSON can be rendered by a user-facing application, laid out by a designer in the editor, and tested by a developer in the playground, and all three operate on the same document. A form defined as React components can be rendered but not visually edited, and a form defined in a design tool can be edited but not imported into code without a translation step.
The schema itself is documented at docs/FORM_SCHEMA.md, which the README calls a form schema and which is the contract you depend on if you integrate this. The topics list bpmn-io, form, form-js, forms and hacktoberfest, which tells you this is a tool that grew through community contributions rather than being designed top down in one go.
The primary language is JavaScript, the default branch is develop, the licence is recorded as a custom one that GitHub cannot classify, and the homepage is the toolkit page at bpmn.io/toolkit/form-js. The last push was on 2026-10-22, and the recent releases are v2.1.0, v2.0.2 and v2.0.1, all in late September 2026, with v2.1.0 and v2.0.2 released within a minute of each other on the same day.
The viewer: import a schema, listen for submit
The viewer is the package most people would use, and its API is small. You construct a Form with a container element, import the schema and the data, and subscribe to the submit event.
import { Form } from '@bpmn-io/form-js';
const form = new Form({
container: document.querySelector('#form'),
});
await form.importSchema(schema, data);
form.on('submit', (event) => {
console.log(event.data, event.errors);
});Three things are worth reading in that snippet.
The constructor takes only a container, so the form is mounted into an element you choose, which means you control where on the page it lives and what surrounds it.
importSchema is awaited, and it takes both the schema and the data. Awaiting it matters because rendering a schema can be asynchronous, and a form that is not yet mounted will not fire the submit event you subscribed to.
The submit handler receives an event with data and errors. That pair is the shape of the whole design. The data is the current state of the form as an object, and the errors is the validation state. A form that has validation problems still submits, and it is up to your handler to decide whether to act on data that came with errors. That is a deliberate choice, since it lets a form show the user what is wrong and still preserve what they typed, rather than discarding the input on a failed validation.
The package.json shows there is a dedicated internationalisation package, form-js-i18n, with its own test target and a translations.json, and the collect-translations script deletes that file, regenerates it by running the viewer and editor test suites with COLLECT_TRANSLATIONS set, and then runs the i18n package's own tests. So the translations are extracted from the code rather than maintained by hand, which is the arrangement you want when a form library has labels spread across every field definition.
The editor and the playground, and why they exist as separate packages
The editor is the same idea applied to the schema rather than the data. You construct a FormEditor with a container and import a schema, which may be a new form or an existing one:
import { FormEditor } from '@bpmn-io/form-js';
const formEditor = new FormEditor({
container: document.querySelector('#form-editor'),
});
await formEditor.importSchema(schema);The parallel with the viewer is deliberate: same constructor shape, same container option, same importSchema call. A developer who has used the viewer already knows the editor.
The playground adds the data on both sides, which is what makes it a testing tool rather than an authoring one:
import { FormPlayground } from '@bpmn-io/form-js';
const formPlayground = new FormPlayground({
container: document.querySelector('#form-playground'),
schema,
data,
});Notice that the playground takes schema and data in the constructor rather than through an import call. That difference in shape is the difference between a tool you open and interact with and a tool you point at a document. The playground is where you try a schema against sample input data and see what comes out the other side, and the demo at demo.bpmn.io/form is the hosted version of that.
For a team, this trio covers a specific workflow: a developer writes the initial schema, a designer adjusts it in the editor, a developer exercises it in the playground with representative data, and the result is imported into the application that renders it with the viewer. The schema is the interface between those people, and the whole project exists to make that interface a JSON file rather than a conversation.
getSchemaVariables: reading what a form consumes and produces
The most useful utility in the package is the one that is easiest to miss, because it is not about rendering at all.
The getSchemaVariables util retrieves the variables defined in a form schema, and the README says it is useful to gather what data is consumed and produced by a form.
import { getSchemaVariables } from '@bpmn-io/form-js';
const variables = getSchemaVariables(schema);
console.log('Schema variables', variables);The two-option form separates the two directions, and the flag semantics are worth noting because they are the opposite of what you might guess:
import { getSchemaVariables } from '@bpmn-io/form-js';
const outputVariables = getSchemaVariables(schema, { inputs: false });
const inputVariables = getSchemaVariables(schema, { outputs: false });Passing inputs: false excludes the inputs, so what comes back is the outputs. Passing outputs: false excludes the outputs, so what comes back is the inputs. Each flag turns off one category rather than selecting one, so a call with neither flag returns both.
This is the piece that makes the schema useful to a system rather than to a person. A form is a contract with an input set and an output set, and being able to read that contract from the schema means a workflow engine, an API layer or a form-to-form mapping can be generated from it instead of being maintained by hand alongside it. That is a much stronger position than the alternative, where the list of fields a form produces is duplicated in the integration code and drifts.
It is also the answer to a question that comes up in any form-driven application: what happens if a field is renamed. With this utility, a build step can compare the schema's variables against what the integration expects and fail loudly, rather than a user discovering that a downstream field is missing.
Building it, and the Node version the README pins
The build section is short, and the environment requirements are stated in a way that will decide whether your setup works.
Build the project in a POSIX environment. On Windows, that is Git Bash or WSL. The README also says that development environments with Node.js version 16 and npm version 8 are currently supported, and encourages using a Node.js version manager such as nvm or n to set up the needed versions.
That Node 16 and npm 8 pairing is worth flagging. It is a specific combination rather than a range, and the repository carries an.nvmrc file at the root, so the version is pinned in the tree as well as described in prose. If you are setting up on a machine with a current Node, the nvm route is the one the project intends, because a newer Node will change the install behaviour and the project has not claimed support for it.
Installation is npm install, and then the scripts. The full set is:
# build the library and run all tests
npm run all
# spin up a single local form-js instance
npm start
# spin up a specific instance
## viewer
npm run start:viewer
## editor
npm run start:editor
## playground
npm run start:playgroundThe all script is a chain: it runs lint, lint:theme, build, lint:types, test and test:distro in sequence. So one command covers linting, the build, type checking, unit tests and the distribution tests, and the package.json expresses that with npm-run-all's run-s.
The individual start scripts exist because there are three packages and you usually want one. The dev script is built on the same primitive: run-s build then dev:pkg with arguments passed through, and there are separate dev targets for the viewer, the editor and the playground.
The build itself fans out across workspaces with npm run build --workspaces --if-present, and the distribution build has its own step, build-distro, which runs prepublishOnly in each workspace. The clean script uses del-cli to remove node_modules and dist across the root, each package and the e2e directory.
Testing goes through Playwright, with a playwright.config.js at the root and an e2e/ directory whose README explains the visual regression tests. A visual regression suite for a form library is the right call, because the output of these components is pixels and a change in a field layout that nobody notices in a unit test is the change users see.
Where form-js is the wrong tool
Three limits, and the first is about who can edit a form.
The first is that the editor is a developer tool. The schema is JSON, and the editor is a visual surface for changing that JSON. A designer who has never written JSON can use the editor, but the file that comes out is still a JSON document that a developer maintains, diffs and reviews. If your requirement is a form builder a non-technical team owns end to end, with the schema stored somewhere they never see, this does not provide it, and the design-versus-development handoff stays in place rather than disappearing.
The second is the environment. The library mounts into a DOM container and the build requires a POSIX environment, so there is no server-side rendering story here and no story for React Native or another non-DOM target. For a Node service that wants to validate a submitted form against a schema, the schema is data and could be read with a different tool, but this package is not that tool. The Node 16 and npm 8 pin also means a project on a current runtime has to run two toolchains during the build, which is a tax on anyone integrating this into a modern monorepo.
The third is the licence. The README says to use the project under the terms of the bpmn.io licence, which is a custom licence that GitHub could not classify. In the bpmn.io family these licences have historically been permissive for open source use with a commercial offering for hosted products, but this article cannot tell you the terms, and the file itself is the thing to read before you commit. What is worth saying is that it is not a standard SPDX identifier, so a corporate licence scanner will flag it and a procurement process will need a human.
The alternative approach for most applications is to write the form in the framework you already use, as components, and skip the schema entirely. The difference in approach is that form-js buys you the editor, the playground and the variable extraction, which together mean the schema is the contract and the tooling around it is maintained by someone else, whereas hand-written components give you total control over behaviour and styling at the cost of having no visual editor and no way to enumerate a form's inputs and outputs except by reading the code. If you have a handful of forms with bespoke behaviour, hand-written components are the smaller dependency. If you have dozens of forms that need to change often and be analysable, the schema approach earns its keep.
Editorial conclusion
Adopt form-js if your application needs forms that a user can fill in and that a developer can also lay out visually, because the JSON schema is the single source of truth for both, and the editor package gives you the visual editing surface that a hand-written form definition never has. Do not adopt it if you need to run it outside a browser, because the package mounts into a DOM container and the build instructions require a POSIX environment, and do not expect a designer-friendly format, because the schema is JSON maintained in code. Verify first that your Node.js version is 16 with npm 8, as the README states that is what is currently supported, that the bpmn.io licence terms suit your use, since the licence is a custom one rather than a standard SPDX identifier, and that the schema variables utility gives you the input and output list you need for integrating a form with the rest of your system.
Frequently asked questions
What is form-js and what packages does it ship?
It is a JavaScript library from bpmn.io to view, visually edit and simulate JSON-based forms. It exports a form viewer, a form editor and a form playground, each also published as its own package under packages/ with its own documentation.
How do I render a form with form-js?
Construct a Form with the container element, await form.importSchema(schema, data), and subscribe to the submit event, whose event carries both data and errors. The container option means you decide where the form mounts on the page.
What does getSchemaVariables do in form-js?
It retrieves the variables defined in a form schema, which tells you what data the form consumes and produces. You can separate the two by passing { inputs: false } to get the outputs or { outputs: false } to get the inputs, since each flag excludes one category rather than selecting one.
What Node.js version does form-js need for development?
The README says development environments with Node.js version 16 and npm version 8 are currently supported, and encourages using a version manager such as nvm or n. The build also requires a POSIX environment, so on Windows you need Git Bash or WSL.
How do I build and run form-js locally?
Run npm install, then npm run all to build the library and run all tests, npm start to spin up a single instance, or npm run start:viewer, npm run start:editor and npm run start:playground for a specific package. The all script chains lint, lint:theme, build, lint:types, test and test:distro.
Under what licence is form-js released?
The README says to use it under the terms of the bpmn.io licence, which is a custom licence that GitHub could not classify rather than a standard SPDX identifier, so the LICENSE file itself is the thing to check before you adopt it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bpmn-io-form-js)Was this useful?