Form2js
Parse browser forms into structured JavaScript objects. Six adapters — React hooks, vanilla DOM, jQuery, FormData, and more. One coherent API.
Install / Use
/learn @maxatwork/Form2jsREADME
form2js
🚀 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.
Description
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.
Documentation
- 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.
Migration from Legacy
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.
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 |
Installation
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 jquery
For browser standalone usage, use script builds where available:
@form2js/dom:dist/standalone.global.js@form2js/jquery:dist/standalone.global.js
Usage
@form2js/dom
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>
@form2js/form-data
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.
@form2js/react
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>
);
}
@form2js/jquery
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>
@form2js/js2form
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 form
Standalone:
- Not shipped as a dedicated global bundle. Use module imports.
@form2js/core
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.
Legacy behavior notes
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.
Design boundaries and non-goals
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 ch
