Reading Form State
Turn form state into a plain object, FormData, or URLSearchParams with toRawData, toFormData, and toURLSearchParams.
When a form submits, you need its values in some concrete shape - a JSON object for an API, a
FormData for a multipart upload, or a query string for a GET request. The form state gives you
three methods for exactly that, all on the same state object you receive in onSubmit (and from the
render prop):
| Method | Returns | Reach for it when… |
|---|---|---|
state.toRawData() | a plain object { [formKey]: value } | sending JSON to an API, or building a domain object. Keeps native types (File, number, boolean). |
state.toFormData() | a FormData instance | multipart uploads or file fields, and Next.js Server Actions. Web only. |
state.toURLSearchParams() | a URLSearchParams instance | a GET request or a shareable/bookmarkable query string. |
All three take the same options object, so once you know one you know all three:
{
include?: (keyof T)[]; // only these fields (others dropped)
exclude?: (keyof T)[]; // drop these fields
transform?: { [key in keyof T]?: (value) => any }; // map a value before it's written
}include wins over exclude if you pass both, and transform runs on top of whichever set remains.
toRawData - a plain object
The default way to read the form. Values keep their JavaScript types, so a number field is a number
and a checkbox is a boolean - no parsing needed. Spread it to add fields the form doesn't own:
onSubmit={(state, e) => {
e.preventDefault();
const payload = { ...state.toRawData(), createdAt: Date.now() };
await fetch("/api/goals", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
}}toFormData - multipart & files
toFormData() builds a FormData from the rendered <form>, keyed by each field's formKey. Because
it reads the real DOM form, file inputs come through as actual File objects - which is what you
want for uploads. Use it for multipart/form-data requests and Next.js Server Actions:
onSubmit={async (state, e) => {
e.preventDefault();
await fetch("/api/upload", { method: "POST", body: state.toFormData() });
// don't set Content-Type - the browser sets the multipart boundary for you
}}toFormData() depends on a native <form> element, so it's not available on React Native.
RNGFormState exposes toRawData() and toURLSearchParams() instead - see
React Native. For Server Actions you usually submit via the action prop
rather than calling toFormData() yourself - see Server Actions.
toURLSearchParams - query strings
toURLSearchParams() is built from toRawData() and then handed to new URLSearchParams(...), so
every value is stringified (42 → "42", true → "true"). That makes it perfect for a GET
search/filter form whose state should live in the URL - shareable and bookmarkable - and a poor fit
for files (a File won't serialize to anything useful). Call .toString() for the query string:
onSubmit={(state, e) => {
e.preventDefault();
const params = state.toURLSearchParams();
router.push(`/search?${params.toString()}`); // /search?q=shoes&size=42&inStock=true
}}Shaping the output with options
include, exclude, and transform work the same across all three methods. Drop a field, keep only
a few, or rewrite a value on the way out:
// Only two fields
state.toRawData({ include: ["email", "name"] });
// Everything except internal fields
state.toFormData({ exclude: ["_csrf"] });
// Map values before serializing
state.toURLSearchParams({
transform: {
q: (value) => value.trim().toLowerCase(),
tags: (value) => value.join(","),
},
});See all three side by side
This form renders each representation live as you type, so you can watch how the same state turns into
an object, FormData, and a query string - and how types are preserved or stringified:
Type in the fields and toggle the checkbox: in toRawData() the age stays a number and the checkbox
a boolean, while toURLSearchParams() shows them as strings.
toRawData() and toURLSearchParams() read field state, so an unchecked box is newsletter: false.
toFormData() reads the native <form>, and the browser omits unchecked checkboxes from FormData
entirely - so the key simply isn't there until it's checked. That's standard FormData behavior,
worth knowing if your backend expects the key to always be present.
See GForm for the form component and
Core Concepts for the full state reference.