When working with the FormData interface in JavaScript, where data is appended as key/value pairs, there’s no built-in way to enforce type safety on the keys you append. This can lead to typos, missing keys, and unexpected runtime errors. But in TypeScript, we can solve this by enforcing strict key validation.
I needed this solution myself when sending my form values to an API. I later realized that I had made several typographical errors in more than one key/value pair I was trying to append to my payload. Because FormData accepts any string as a key, I was able to pass in the wrong strings and proceed with the API request.
After this happened, I looked for a way to ensure that TypeScript doesn’t allow those errors.
This article will show you how to make FormData
keys type-safe using TypeScript.
Prerequisites
To get the most out of this article, you should have a basic understanding of the following:
-
JavaScript programming
-
TypeScript fundamentals, especially how interfaces, types, and the
keyof
operator work -
the FormData interface
If you’re new to TypeScript or FormData, I recommend checking out TypeScript’s official documentation and MDN’s guide on FormData before proceeding.
Step 1: Define Your Allowed Keys
The Old Way
The default way of appending data with FormData is to do it manually, with plain strings:
const payload = new FormData();
payload.append("id", "1122");
payload.append("name", "Clark Kent");
payload.append("agge", "36");
In the code snippet above, you can see that there was a typo when defining a key for age
. But TypeScript won’t flag it as an error, and this could lead to errors when this data is sent with an API request.
The Better Way
Instead of manually typing out the keys, define them in an object schema with a TypeScript interface.
interface MyAllowedData {
id: number;
name: string;
age: number;
}
Alternatively, you can define them with types:
type MyAllowedData = {
id: number;
name: string;
age: number;
}
You can use either types or interfaces, it’s just a matter of preference. You can find out more about how they differ in this official TypeScript documentation playground.
Next, define a union type from each key in your interface.
type MyFormDataKeys = keyof MyAllowedData
The keyof
operator helps to create a union type of an object type’s keys, so it comes in really handy if you don’t want to manually define a union type for a larger object with many keys.
Step 2: Create an Append Helper Function
Now that you’ve defined your strictly-typed keys, the next step is to create a helper function that ensures only valid keys are appended to FormData.
function appendToFormData (formData: FormData, key: MyFormDataKeys, value: string) {
formData.append(key, value);
};
The appendToFormData
function takes in three arguments. Here’s how it all works:
-
The first argument,
formData
, is an instance of the FormData object. This is where key/value pairs will be appended before sending them in an API request. -
The second argument,
key
, is the key name of the field you want to append. Its type is ofMyFormDataKeys
, the union type we created to ensure only those keys we defined are appended to FormData. -
The third argument is a string
value
which represents the value to be appended with the key.
Note that FormData only accepts the string
and Blob
types as values in each key/value pair. In this guide, we’re only working with string values – but keep in mind that you can use blob values for appending files to API requests.
Now, let’s test out the function:
const payload = new FormData();
appendToFormData(payload, "id", "19282");
appendToFormData(payload, "name", "Lenny Brown");
appendToFormData(payload, "age", "20");
appendToFormData(payload, "someOtherKey", "89");
Step 3: Use the Helper Function after Form Submission
Now let’s append our fields to FormData before sending them to an API.
const handleSubmitForm = () => {
const payload = new FormData();
appendToFormData(payload, "id", "19282");
appendToFormData(payload, "name", "Lenny Brown");
appendToFormData(payload, "age", "20");
fetch("/api/submit", { method: "POST", body: payload });
};
Appending Fields from an Object
Alternatively, if you already have your entire payload in an object, you can avoid appending each field one by one by implementing the function like this:
const handleSubmitForm = () => {
const formValues: MyAllowedData = {
id: 1123,
name: 'John Doe',
age: 56
}
const payload = new FormData();
Object.entries(formValues).forEach(([key, value]) => {
appendToFormData(payload, key as MyFormDataKeys, `${value}`);
});
fetch("/api/submit", { method: "POST", body: payload });
};
In the snippet above, we’re using Object.entries
to iterate over each key/value pair in an object so it can be appended to the FormData object. Note that the value in each pair, whether it’s a string or a number, is passed as a string using template literals to prevent a TypeScript type mismatch from the value
argument in our helper function.
Conclusion
By leveraging TypeScript’s keyof
operator, we can make FormData.append()
fully type-safe. This simple technique helps prevent key mismatches and makes your API requests more reliable.
Let me know your thoughts about the article, and feel free to make any suggestions you think could improve my solution.
Thanks for reading!