Decode FormData to JavaScript Object
`decode-formdata` is a JavaScript and TypeScript library designed to convert a flat `FormData` object back into a complex, nested JavaScript object. This process addresses the inherent data loss when `FormData` serializes all values to strings or `File` objects, and flattens nested structures. The library, currently at version 0.9.0, is actively maintained with recent updates focusing on bug fixes, security enhancements, and improved parsing capabilities like array bracket notation. It does not follow a strict release cadence but receives updates as needed. Its primary utility lies in reconstructing type information (booleans, numbers, dates) and object/array hierarchies from form field names using dot and bracket notation. This makes it particularly valuable for server-side processing in full-stack frameworks (e.g., Next.js, Remix, SvelteKit) where `FormData` is common for progressively enhanced forms, allowing developers to easily validate and type the re-hydrated data with schema libraries like Zod or Valibot.
Common errors
-
TypeError: formData.entries is not a function
cause `decode` expects an actual `FormData` instance or an object that implements the `entries()` method compatible with the `FormData` interface. This error often occurs when a plain JavaScript object or an incorrect type is passed.fixEnsure you are passing a proper `FormData` object to the `decode` function. In Node.js, if not using Node 16+ (which has global `FormData`), you might need to import a polyfill like `@remix-run/node`'s `FormData` or `node-fetch`'s `FormData`. -
Unexpected string value for a field expected to be a number/boolean/date/file.
cause This typically happens when a field's type (e.g., 'price' as a number) is not correctly specified in the options object passed to `decode`, or the path is incorrect. The library defaults to strings if a type isn't specified.fixCarefully review the `options` object in your `decode` call. Ensure that `arrays`, `booleans`, `dates`, `files`, and `numbers` arrays contain the correct paths for all fields that should be converted from their string `FormData` representation to the desired type. Remember to use `$` for nested array paths. -
Arrays are not being parsed correctly or are appearing as objects with numeric keys.
cause This is a common issue when `decode` is not explicitly told which fields represent arrays. By default, `decode-formdata` will parse `foo.0`, `foo.1` as an object `{ foo: { '0': ..., '1': ... } }` if `foo` is not listed in the `arrays` option.fixAdd the root path of the array to the `arrays` option in the `decode` function call. For example, if you have fields like `tags.0`, `tags.1`, ensure `tags` is in the `arrays` option: `decode(formData, { arrays: ['tags'], ... })`.
Warnings
- breaking The output behavior for empty strings (`''`), the string `'null'`, and the string `'undefined'` changed. Previously, these might have been treated differently, but now their interpretation might result in different values in the decoded object.
- gotcha Version 0.9.0 introduced a fix for potential prototype pollution by ignoring certain keys during decoding. While this is a security enhancement, it means that `FormData` entries with keys like `__proto__` or `constructor.prototype` will now be explicitly ignored and will not appear in the decoded object. This is a positive change for security but could be a behavioral shift for applications that, for unusual reasons, relied on these keys being processed.
- gotcha When specifying paths for deeply nested arrays (e.g., `images.$.created`), the `$` symbol is crucial as a wildcard for array indices. Forgetting to use `$` will lead to incorrect type parsing for elements within the array.
Install
-
npm install decode-formdata -
yarn add decode-formdata -
pnpm add decode-formdata
Imports
- decode
const decode = require('decode-formdata');import { decode } from 'decode-formdata'; - FormDataEntry
import { FormDataEntry } from 'decode-formdata';import type { FormDataEntry } from 'decode-formdata'; - FormDataInfo
import { FormDataInfo } from 'decode-formdata';import type { FormDataInfo } from 'decode-formdata';
Quickstart
import { decode } from 'decode-formdata';
// In a Node.js environment, you might need to polyfill FormData
// or use a library like 'form-data-polyfill' if not using Node 16+ which has it globally.
// For browser or modern environments (like Cloudflare Workers, Deno, Bun), FormData is globally available.
async function handleFormSubmission() {
// Simulate receiving FormData from an HTTP request (e.g., request.formData() in a web framework)
const formData = new FormData();
// Populate the FormData object with various types of data and nesting
formData.append('title', 'Red apple');
formData.append('price', '0.89');
formData.append('created', '2023-10-09');
formData.append('active', 'on'); // 'on' is a common representation for true from checkboxes
formData.append('tags.0', 'fruit');
formData.append('tags.1', 'healthy');
formData.append('tags.2', 'sweet');
formData.append('images.0.title', 'Close up of an apple');
formData.append('images.0.created', '2023-08-24');
formData.append('images.0.file', new Blob(['image-data-1'], { type: 'image/png' }), 'apple.png');
formData.append('images.1.title', 'Our fruit fields at Lake Constance');
formData.append('images.1.created', '2023-08-12');
formData.append('images.1.file', new Blob(['image-data-2'], { type: 'image/jpeg' }), 'fields.jpg');
// Decode the FormData into a JavaScript object, specifying expected types and array paths
const formValues = decode(formData, {
arrays: ['tags', 'images'], // 'tags' is a simple array, 'images' is an array of objects
booleans: ['active'], // 'active' field should be parsed as a boolean
dates: ['created', 'images.$.created'], // 'created' and 'images[i].created' fields as Date objects
files: ['images.$.file'], // 'images[i].file' fields as File/Blob objects
numbers: ['price'], // 'price' field as a number
});
console.log('Decoded Form Values:', formValues);
// Expected output structure:
// {
// title: 'Red apple',
// price: 0.89,
// created: <Date object for 2023-10-09>,
// active: true,
// tags: ['fruit', 'healthy', 'sweet'],
// images: [
// {
// title: 'Close up of an apple',
// created: <Date object for 2023-08-24>,
// file: <Blob object for apple.png>
// },
// {
// title: 'Our fruit fields at Lake Constance',
// created: <Date object for 2023-08-12>,
// file: <Blob object for fields.jpg>
// }
// ]
// }
}
handleFormSubmission();