npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

form-codec

v1.0.2

Published

FormData round-trips your objects — Server Actions lose your types back to strings, this doesn't.

Readme

form-codec

FormData round-trips your objects — Server Actions lose your types back to strings, this doesn't.

npm version License: MIT

Convert JavaScript objects to FormData and back, perfectly reconstructing booleans, numbers, dates, nulls, arrays, and deeply nested structures without losing type fidelity.

Built specifically for React 19 / Next.js / Remix Server Actions where Object.fromEntries(formData) silently corrupts non-string types.

Why form-codec?

Browsers and modern frameworks (like Next.js Server Actions, Remix, and React 19 Actions) natively turn everything in a form into a string. A checkbox becomes "true", a number becomes "25", and a date becomes a raw ISO string, leaving you with FormData losing types in your Next.js Server Action. form-codec fixes this by remembering the original types, so decode() gives back real booleans, numbers, and dates — not strings you have to manually convert yourself. It is the only package that does both directions (object → FormData and FormData → object) with full type fidelity, whereas alternatives only go one way.

Quick Start

Step 1: Install

npm install form-codec

Step 2: Import

import { encode, decode } from "form-codec";

Step 3: Use

const original = { name: "Alice", active: true, age: 30 };

const formData = encode(original);
// ... transmit formData to your server ...

const restored = decode(formData);
console.log(restored.active); // true (boolean, not "true"!)
console.log(restored.age);    // 30 (number, not "30"!)

1. The Type-Loss Bug

Server Actions naturally receive raw FormData, which only supports strings and File objects.

The broken way (native):

// ❌ Server Actions lose your types
const data = { active: true, age: 25, date: new Date() };

// 1. You submit it via Server Action or fetch...
// 2. The server reconstructs it:
const broken = Object.fromEntries(formData);
// { active: "true", age: "25", date: "2024-01-01T..." } 
// 🚨 Booleans are strings, Numbers are strings, Dates are strings, Arrays are missing!

The form-codec way:

import { encode, decode } from "form-codec";

// ✅ Types are preserved perfectly
const fd = encode({ active: true, age: 25, date: new Date() });
const fixed = decode(fd); 
// { active: true, age: 25, date: Date }

2. Why it exists

While existing tools like object-to-formdata excel at one-way serialization, they offer no standard way to deserialize back to the exact same types. form-codec embeds a tiny, hidden type-marker (__form-codec) into the FormData during serialization, allowing the server to losslessly reconstruct the original object tree with zero guessing or manual parsing.

3. Install

npm install form-codec

Zero dependencies. ESM & CommonJS supported.

4. Basic Usage

import { encode, decode } from "form-codec";

// Client-side
const formData = encode({ name: "Alice", active: true });
await myServerAction(formData);

// Server-side
async function myServerAction(formData: FormData) {
  const data = decode(formData);
  // data.active === true
}

5. Nested Object Example

const user = {
  profile: {
    city: "New York",
    zip: 10001
  }
};

const fd = encode(user);
// fd.get('profile[city]') === "New York"
// fd.get('profile[zip]') === "10001"

const decoded = decode(fd); // { profile: { city: "New York", zip: 10001 } }

6. Array Example

const form = encode({ tags: ["react", "nextjs"] });
// fd.getAll('tags[]') === ["react", "nextjs"]

const decoded = decode(form); // { tags: ["react", "nextjs"] }

7. File Upload Example

const file = new File(["content"], "document.txt", { type: "text/plain" });

const fd = encode({
  title: "My Upload",
  documents: [file]
});

const decoded = decode(fd); 
// decoded.documents[0] is a File object!

8. Configuration

Both encode and decode accept an options object.

const fd = encode(data, {
  arrayFormat: "brackets", // "brackets" | "indexed" | "comma"
  objectFormat: "brackets", // "brackets" | "dot"
  nullHandling: "skip", // "skip" | "empty" | "stringify"
  undefinedHandling: "skip", // "skip" | "empty"
  maxDepth: 32,
  include: ["name", "email"],
  exclude: ["internal_id"],
  rename: { "name": "fullName" },
  transform: {
    "age": (val) => Number(val) + 1
  }
});

9. Serialization Strategies

By default, form-codec uses brackets notation (tags[] and profile[city]), which is the universally accepted standard for FormData arrays and nested properties in PHP, Ruby on Rails, and Express.

You can switch to dot-notation or indexed arrays:

encode(data, { objectFormat: "dot", arrayFormat: "indexed" });
// Result: profile.city="New York", tags[0]="react", tags[1]="nextjs"

10. Framework Entry Points

form-codec ships with subpath exports specifically tailored for bundler tree-shaking and SEO. For now, they mirror the core API, but will expand with framework-specific wrappers:

import { encode, decode } from "form-codec/next";   // Next.js Server Actions
import { encode, decode } from "form-codec/react";  // React 19 Actions (useActionState)
import { encode, decode } from "form-codec/remix";  // Remix Actions

11. TypeScript Usage

form-codec is written in TypeScript and provides generic types for fully typed decoding:

type User = { id: number; name: string };
const user = decode<User>(formData);
// user.id is correctly inferred as number

12. Browser and Node Compatibility

Works flawlessly in the Browser and Node.js 18+ (which includes native FormData, File, and Blob).

13. Error Handling

form-codec fails safely:

  • Circular References: Throws an error immediately to prevent infinite recursion, unlike naive stringifiers.
  • Max Depth: Prevents stack overflows on malicious deep payloads (default depth: 32).

14. Performance

Fast and memory-efficient. form-codec iterates over objects in a single pass without large intermediate ASTs.

15. Known Limitations

Empty Collections Are Skipped
Empty arrays ([]) and empty objects ({}) are skipped during encoding because FormData only encodes key-value pairs. During decoding, they are indistinguishable from a field that was never set.

Null/Undefined Behavior with "skip"
By default, null and undefined values are skipped (nullHandling: "skip"). When skipped, they are omitted from the FormData. When decoded, these properties will simply not exist on the reconstructed object, which typically evaluates to undefined. If you want to accurately reconstruct null versus undefined, you must explicitly configure { nullHandling: "empty", undefinedHandling: "empty" } (or "stringify" for nulls).

Key-Collision with Format Syntax
form-codec parses string keys containing brackets (tags[]) and dots (profile.name) into nested paths. If you have an object with a literal key name that mimics this syntax (e.g., { "a.b": "value" } or { "tags[]": "value" }), it cannot be distinguished from a nested path during decoding, and will be reconstructed as { a: { b: "value" } }. Do not use literal periods or brackets in your root keys.

16. API Reference

encode(data: Record<string, any>, options?: EncodeOptions): FormData

Converts a JavaScript object to a FormData instance, appending a hidden __form-codec type marker.

decode<T>(formData: FormData, options?: DecodeOptions): T

Reconstructs a JavaScript object from a FormData instance using the embedded type marker.

17. FAQ

Q: Does it support BigInt? Yes, BigInt is fully supported and properly decoded.

Q: Can I use this without the type marker? Yes, if __form-codec is missing, it will decode nested structures safely, though primitives will remain strings unless you set coerce: true in decode options.

18. Contributing

Contributions are welcome! Please open an issue before submitting a large PR.

License

MIT ©