🚀 form2js is back — modernized and actively maintained.
Originally created in 2010, now rewritten for modern JavaScript, TypeScript, ESM, React, and modular usage.
Legacy version is available in the legacy branch.
Migrating from legacy form2js? Start with the migration guide.
A small family of packages for turning form-shaped data into objects, and objects back into forms.
It is not a serializer, not an ORM, and not a new religion. It just does this one job, does it reliably, and leaves before anyone starts a committee about it.
- Docs Site - overview, installation, unified playground, and published API reference.
- Migration Guide - map old
form2jsandjquery.toObjectusage to the current package family. - API Reference Source - markdown source for the published API docs page.
If you are moving from the archived single-package version, start with the migration guide.
Quick package map:
- Legacy browser
form2js(...)usage ->@form2js/dom - Legacy jQuery
$("#form").toObject()usage ->@form2js/jquery - Server or pipeline
FormDataparsing ->@form2js/form-data - React submit handling ->
@form2js/react - Object back into fields ->
@form2js/js2form
The current project keeps the naming rules and core parsing model, but splits the old browser-era API into environment-specific packages.
| Package | npm | Purpose | Module | Standalone | Node.js |
|---|---|---|---|---|---|
@form2js/react |
React submit hook with parsing/validation state | Yes | No | Browser-focused | |
@form2js/dom |
Extract DOM fields to object (formToObject, form2js) |
Yes | Yes | With DOM shim (jsdom) |
|
@form2js/form-data |
Convert FormData/entries to object |
Yes | No | Yes | |
@form2js/js2form |
Populate DOM fields from object (objectToForm, js2form) |
Yes | No | With DOM shim (jsdom) |
|
@form2js/core |
Path parsing and object transformation engine | Yes | No | Yes | |
@form2js/jquery |
jQuery plugin adapter ($.fn.toObject) |
Yes | Yes | Browser-focused |
Install only what you need:
npm install @form2js/react react
npm install @form2js/dom
npm install @form2js/form-data
npm install @form2js/js2form
npm install @form2js/core
npm install @form2js/jquery jqueryFor browser standalone usage, use script builds where available:
@form2js/dom:dist/standalone.global.js@form2js/jquery:dist/standalone.global.js
HTML used in examples:
<form id="profileForm">
<input name="person.name.first" value="Esme" />
<input name="person.name.last" value="Weatherwax" />
<label
><input type="checkbox" name="person.tags[]" value="witch" checked />
witch</label
>
</form>Module:
import { formToObject } from "@form2js/dom";
const result = formToObject(document.getElementById("profileForm"));
// => { person: { name: { first: "Esme", last: "Weatherwax" }, tags: ["witch"] } }Standalone:
<script src="https://unpkg.com/@form2js/dom/dist/standalone.global.js"></script>
<script>
const result = formToObject(document.getElementById("profileForm"));
// or form2js(...)
</script>Module (browser or Node 18+):
import { formDataToObject } from "@form2js/form-data";
const fd = new FormData(formElement);
const result = formDataToObject(fd);Node.js note:
- Node 18+ has global
FormData. - You can also pass iterable entries directly, which is handy in server pipelines:
import { entriesToObject } from "@form2js/form-data";
const result = entriesToObject([
["person.name.first", "Sam"],
["person.roles[]", "captain"],
]);
// => { person: { name: { first: "Sam" }, roles: ["captain"] } }With schema validation (works with Zod or any { parse(unknown) } schema):
import { z } from "zod";
import { formDataToObject } from "@form2js/form-data";
const PersonSchema = z.object({
person: z.object({
age: z.coerce.number().int().min(0)
})
});
const result = formDataToObject([["person.age", "42"]], {
schema: PersonSchema
});
// => { person: { age: 42 } }Standalone:
- Not shipped for this package. Use module imports.
Module:
import { z } from "zod";
import { useForm2js } from "@form2js/react";
const FormDataSchema = z.object({
person: z.object({
name: z.object({
first: z.string().min(1)
})
})
});
export function ProfileForm(): React.JSX.Element {
const { onSubmit, isSubmitting, isError, error, isSuccess, reset } = useForm2js(
async (data) => {
// data is inferred from schema when schema is provided
await sendFormData(data);
},
{
schema: FormDataSchema
}
);
return (
<form
onSubmit={(event) => {
void onSubmit(event);
}}
>
<input name="person.name.first" defaultValue="Sam" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Saving..." : "Save"}
</button>
{isError ? <p>{String(error)}</p> : null}
{isSuccess ? <p>Saved</p> : null}
<button type="button" onClick={reset}>
Reset state
</button>
</form>
);
}HTML used in examples:
<form id="profileForm">
<input name="person.name.first" value="Sam" />
<input name="person.name.last" value="Vimes" />
</form>Module:
import $ from "jquery";
import { installToObjectPlugin } from "@form2js/jquery";
installToObjectPlugin($);
const data = $("#profileForm").toObject({ mode: "first" });
// => { person: { name: { first: "Sam", last: "Vimes" } } }Standalone:
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://unpkg.com/@form2js/jquery/dist/standalone.global.js"></script>
<script>
const data = $("#profileForm").toObject({ mode: "combine" });
</script>HTML used in examples (before calling objectToForm):
<form id="profileForm">
<input name="person.name.first" />
<input name="person.name.last" />
</form>Module:
import { objectToForm } from "@form2js/js2form";
objectToForm(document.getElementById("profileForm"), {
person: { name: { first: "Tiffany", last: "Aching" } },
});
// fields are now populated in the formStandalone:
- Not shipped as a dedicated global bundle. Use module imports.
Module:
import { entriesToObject, objectToEntries } from "@form2js/core";
const data = entriesToObject([
{ key: "person.name.first", value: "Vimes" },
{ key: "person.tags[]", value: "watch" },
]);
const pairs = objectToEntries(data);Node.js:
- Fully supported (no DOM dependency).
Standalone:
- Not shipped for this package. Use module imports.
Compatibility with the old project is intentional.
- Name paths define output shape (
person.name.first). - Array and indexed syntax is preserved (
items[],items[5].name). - Rails-style names are supported (
rails[field][value]). - DOM extraction follows native browser form submission semantics for checkbox and radio values.
- Unsafe key path segments (
__proto__,prototype,constructor) are rejected by default. - This library does data shaping, not JSON/XML serialization.
These boundaries are intentional and are used for issue triage.
- Sparse indexes are compacted in first-seen order (
items[5],items[8]->items[0],items[1]). - Type inference is minimal by design; DOM extraction keeps native string values instead of coercing checkbox/radio fields.
- Unchecked indexed controls are omitted and therefore do not reserve compacted array slots; include another submitted field when row identity matters.
formToObjectreads successful form control values, not option labels. Disabled controls (including disabled fieldset descendants) and button-like inputs are excluded unless you explicitly opt in to disabled values.extractPairs/formToObjectsupportnodeCallback; returnSKIP_NODEto exclude a node entirely, or{ name|key, value }to inject a custom entry.- Parser inputs reject unsafe path segments by default. Use
allowUnsafePathSegments: trueonly with trusted inputs. objectToFormsupportsnodeCallback; returningfalseskips the default assignment for that node.objectToFormsets form control state and values; it does not dispatch syntheticchangeorinputevents.- Empty collections are not synthesized when no matching fields are present (for example, unchecked checkbox groups).
- Dynamic key/value remapping (for example, converting
key/valfields into arbitrary object keys) is application logic. - For file payloads and richer multipart semantics, use
FormDataand@form2js/form-data.
npm cinpm run lint
npm run typecheck
npm run test
npm run build
npm run pack:dry-runnpm run docs
npm run docs:buildThe homepage includes the unified playground for @form2js/react, @form2js/dom, @form2js/jquery, @form2js/js2form, @form2js/core, and @form2js/form-data.
The published docs site is deployed by .github/workflows/pages.yml.
- Trigger: pushes to
masterthat touchapps/docs/**,docs/**,packages/**,.github/workflows/pages.yml,package.json,package-lock.json,turbo.json, ortsconfig.base.json, plus manualworkflow_dispatch. - Output:
apps/docs/dist. - URL:
https://maxatwork.github.io/form2js/.
In repository settings, set Pages source to GitHub Actions once, and then the workflow handles updates.
- Keep changes focused to one problem area where possible.
- Add or update tests for behavior changes.
- Add a changeset (
npm run changeset) for user-visible changes. - Include migration notes in README if behavior or API changes.
Please include:
- Clear expected vs actual behavior.
- Minimal reproduction (HTML snippet or input entries).
- Package name and version.
- Environment (
node -v, browser/version if relevant).
- CI runs lint, typecheck, test, build, and package dry-run.
- Releases are managed with Changesets and the published
@form2js/*packages are versioned in lockstep.
Default scope is @form2js/*.
If you need to publish under another scope:
npm run scope:rewrite -- --scope @your-scope --dry-run
npm run scope:rewrite -- --scope @your-scopeThis rewrites package names, internal dependencies, and import references.
MIT, see LICENSE.