Skip to content

Form object

FormObject turns an HTMLFormElement into a structured plain object β€” and writes one back β€” using the control name to shape the result. It is a static snapshot, like new FormData(), with no reactivity or dependencies. Type into the form below and watch the object update.

Field names shape the object

The name attribute is a path. Dots nest objects, numeric segments build arrays, and a trailing [] marks an append array.

import { FormObject } from "elements-kit/utilities/form-object";
// <input name="user.name"> β†’ { user: { name } }
// <input name="user.address.city">β†’ { user: { address: { city } } }
// <input name="tags.0"> β†’ { tags: [ "…" ] } (explicit index)
// <input name="colors[]"> β†’ { colors: [ "…" ] } (append, one per control)
const data = new FormObject(form).toObject();

A trailing [] always produces an array β€” a single colors[] control gives ["x"], and a group with none checked still serializes to []. Use it whenever a field is conceptually a list.

Reading a form

toObject() walks the form’s named controls and returns the nested object. It mirrors native form submission: disabled and unchecked controls are dropped, <select multiple> yields an array, and <input type="file"> yields File objects. toJSON() is an alias, so JSON.stringify(instance) works.

const form = document.querySelector("form")!;
const data = new FormObject(form).toObject();

Writing back

fromObject(data) assigns values from a nested object onto matching controls β€” setting .value, checking boxes and radios, and selecting options. Missing paths are left untouched. clear() resets every named control. Both return this for chaining.

new FormObject(form).fromObject({
user: { name: "Wael" },
colors: ["red", "blue"], // checks the matching colors[] boxes
});

Transforms

Extraction runs each field through a pipeline of FormFieldTransforms. Each transform receives the field ({ control, name, value, checked? }) and returns it, a modified copy, or null to drop it. The default pipeline skips disabled controls, buttons, and unchecked boxes:

import {
FormObject,
defaultTransforms,
uncheckedAs,
skipEmpty,
} from "elements-kit/utilities/form-object";
// Replace the defaults β€” include unchecked checkboxes as `false`
new FormObject(form, {
transforms: [...defaultTransforms.slice(0, 2), uncheckedAs(false)],
}).toObject();
// Extend β€” drop empty fields and trim strings
new FormObject(form, {
transforms: [
...defaultTransforms,
skipEmpty,
(f) => ({ ...f, value: typeof f.value === "string" ? f.value.trim() : f.value }),
],
}).toObject();

Built-ins: skipDisabled, skipButtons, dropUnchecked (the defaults), plus uncheckedAs(value) and skipEmpty. Passing transforms replaces the defaults β€” spread ...defaultTransforms to keep them.

Field names that walk an object’s prototype (__proto__, prototype, constructor) are ignored, so a malicious name cannot pollute Object.prototype.

See also