A schema-aware reactive FormData builder with Zod validation and full Blob/File support. Manage form state, validate data, and generate submission-ready FormData with a reactive proxy.
Copy the src/index.ts
into your project if you don't want to add additional dependencies to your project or
npm install frmz zod
pnpm add frmz zod
Note: Zod is a peer dependency and must be installed separately.
import { frmz } from "frmz";
// Create a form manager with automatic schema inference
const { data, getFormData } = frmz({
user: {
name: "Alice",
email: "[email protected]",
age: 30,
},
preferences: {
newsletter: true,
tags: ["tech", "programming"],
},
});
// Update values reactively
data.user.name = "Bob"; // Changes are tracked
data.preferences.tags.push("javascript");
// Generate FormData for submission
const formData = getFormData();
// formData contains: user[name]=Bob, user[email][email protected], etc.
// Handle file inputs with full type safety
document.getElementById("avatar").addEventListener("change", (e) => {
const file = e.target.files[0];
const { data, getFormData } = frmz({
profile: {
name: "John Doe",
avatar: file, // File object handled properly
documents: [file], // Works in arrays too
metadata: {
uploadDate: new Date().toISOString(),
},
},
});
// Submit to server
fetch("/api/upload", {
method: "POST",
body: getFormData(), // Contains the file properly
});
});
import { z } from "zod";
import { frmz } from "frmz";
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
avatar: z.instanceof(Blob).optional(),
age: z.number().min(18),
tags: z.array(z.string()),
});
const { data, getFormData } = frmz(
{
name: "Alice",
email: "[email protected]",
age: 25,
tags: ["developer"],
avatar: someFile, // Optional blob
},
userSchema // Custom schema override
);
frmz(initialData)
Creates a form manager with inferred schema.
Parameters:
initialData
: Object or array to use as initial stateReturns:
data
: Reactive proxy of the initial datagetFormData()
: FormData: Function that returns current state as FormDatafrmz(initialData, schema)
Creates a form manager with custom Zod schema.
Parameters:
initialData
: Data matching the provided schemaschema
: Zod schema for validationReturns:
data
: Reactive proxy of validated datagetFormData()
: FormData: Function that returns current state as FormDataThe data
object is a deep proxy that tracks changes:
const { data, getFormData } = frmz({
user: { name: "Alice", settings: { darkMode: true } },
});
// All changes are tracked
data.user.name = "Bob"; // Simple property
data.user.settings.darkMode = false; // Nested property
data.user.settings.fontSize = 16; // New properties
// getFormData() captures all current changes
const formData = getFormData(); // Contains latest state
The generated FormData uses standard encoding:
Object properties:
user[name]=Alice
user[email][email protected]
Array elements:
tags[0]=tech
tags[1]=programming
File uploads:
tags[0]=tech
tags[1]=programming
DeepWritable<T>
Utility type to make deeply nested readonly types writable:
import type { DeepWritable } from "frmz";
type User = DeepWritable<typeof userSchema>;
// Now all properties are mutable
The library throws Zod validation errors when provided data doesn't match the schema:
try {
const manager = frmz(
{ email: "invalid-email" },
z.object({ email: z.string().email() })
);
} catch (error) {
console.error("Validation failed:", error.errors);
}
Form State Management
const { data, getFormData } = frmz(initialFormState);
// Bind to input events
input.addEventListener("change", (e) => {
data.user.name = e.target.value;
});
// Submit handler
form.addEventListener("submit", async (e) => {
e.preventDefault();
await fetch("/submit", { body: getFormData() });
});
File Upload Forms
const { data, getFormData } = frmz({
title: "",
description: "",
attachments: [], // Will hold files
});
// Add files to array
fileInput.addEventListener("change", (e) => {
data.attachments.push(...e.target.files);
});
Configuration Objects
const { data, getFormData } = frmz({
settings: {
theme: "dark",
notifications: true,
layout: { grid: true, spacing: 2 },
},
});
Root Types: Only objects and arrays are supported as root values
FormData Encoding: Uses standard multipart/form-data encoding (not JSON)
Circular References: Not supported in the data structure
Server Processing: Requires server-side support for bracket notation (e.g.,user[name]
)
Works in all modern browsers that support:
Proxy API (ES2015)
FormData API
Blob/File API
Found an issue? Want to add a feature? Please open an issue or PR on our GitHub repository.
MIT License - feel free to use in your projects!