Un formulaire de dix champs, et la tentation de lire chaque valeur une par une avec un sélecteur. Le code fonctionne, puis un champ change de nom et personne ne s'en aperçoit.
Le navigateur sait déjà collecter ces valeurs : c'est ce qu'il fait quand un formulaire part sans JavaScript. Un objet natif donne accès à ce travail.
Définition
FormData représente un ensemble de couples nom et valeur, exactement ceux qu'un formulaire enverrait. Construit à partir d'un élément de formulaire, il en lit tous les champs d'un coup. Construit vide, il se remplit à la main.
const formulaire = document.querySelector("form");
const donnees = new FormData(formulaire);
console.log(donnees.get("email")); // "camille@exemple.fr"
console.log(donnees.getAll("interet")); // ["design", "code"]
donnees.append("source", "newsletter");
console.log(Object.fromEntries(donnees));Un même nom peut porter plusieurs valeurs, ce qu'aucun objet simple ne sait représenter. get rend la première, getAll les rend toutes, et Object.fromEntries ne conserve que la dernière.
Les champs qui n'y sont pas
La collecte suit les règles du HTML, pas celles du code. Quatre catégories de champs sont écartées sans le moindre avertissement.
- Sans attribut name : un champ qui n'en porte pas n'existe pas pour le formulaire.
- Désactivé : l'attribut
disabledretire le champ de l'envoi. - Case non cochée : rien n'est envoyé, il n'y a pas de valeur fausse.
- Bouton d'envoi : sa valeur n'apparaît que si on le passe en second argument du constructeur.
La première ligne explique la moitié des envois vides signalés en développement : le champ est visible, rempli, et pourtant absent des données.
Envoyer un formulaire, fichiers compris
L'objet part tel quel dans le corps d'un fetch(). C'est le seul moyen simple de transmettre un fichier choisi par un visiteur.
formulaire.addEventListener("submit", async (evenement) => {
evenement.preventDefault();
const reponse = await fetch("/api/inscriptions", {
method: "POST",
body: new FormData(formulaire),
});
console.log(reponse.ok);
});L'appel à preventDefault() empêche le rechargement classique de la page, sans quoi le navigateur enverrait le formulaire lui-même.
Ne posez jamais l'en-tête Content-Type à la main avec un envoi de ce type. Le navigateur doit y ajouter une frontière calculée qui sépare les champs ; écrite manuellement, elle est fausse et le serveur ne lit plus rien.
Questions fréquentes
Comment envoyer les mêmes données en JSON ?
Convertissez d'abord l'ensemble en objet, puis sérialisez-le : JSON.stringify(Object.fromEntries(donnees)). Attention aux champs à valeurs multiples, qui se réduisent alors à une seule valeur, et aux fichiers, qui ne survivent pas à la conversion. Pour un envoi en paramètres d'URL, new URLSearchParams(donnees) fait le travail directement.
Peut-on lire le contenu du fichier avant l'envoi ?
Oui, la valeur associée à un champ de type fichier est un objet File, avec son nom, sa taille et son type. Ses méthodes text() et arrayBuffer() rendent une promesse sur le contenu, ce qui permet un aperçu ou une vérification de poids avant tout appel réseau.
Pourquoi mon champ apparaît-il deux fois ?
Parce que append ajoute une valeur sans remplacer celles qui existent déjà, contrairement à set qui écrase tout ce qui porte ce nom. Un envoi rejoué après un échec, sur un objet réutilisé, duplique donc les valeurs. Reconstruisez l'objet à chaque tentative plutôt que de le corriger.