Construire une URL avec paramètres qui survit à un copier-coller
Publié le 11/08/2026 · 12 min de lecture · Outils pour développeurs
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez OneKitly
Performance web · Formats de fichiers
Vérifié à partir de 4 sources
Une chaîne de requête casse quand un caractère qui signifie quelque chose pour l'URL — une espace, une esperluette, un signe égal, un dièse — reste tel quel à l'intérieur d'une valeur. L'encodage pour cent règle cela, mais il existe trois encodages qui divergent, et la divergence se voit sur l'espace. encodeURIComponent écrit une espace %20 et laisse ! ' ( ) * ~ intacts. Le sérialiseur application/x-www-form-urlencoded, celui qu'utilisent URLSearchParams et tous les formulaires HTML, écrit une espace + et encode ! ' ( ) ~ en laissant * intact. La RFC 3986 stricte encode tout ce qui sort de A-Z a-z 0-9 - . _ ~, elle échappe donc aussi le * et garde le ~. Ce générateur propose les trois, et son mode formulaire a été comparé à URLSearchParams de Node sur dix-sept valeurs, dont un émoji, un saut de ligne et une chaîne accentuée : identique octet pour octet à chaque fois. Il double bien l'encodage : saisis %20 comme valeur et tu obtiens %2520. C'est correct — tu saisis une valeur brute, pas une valeur déjà encodée — et l'analyseur de l'outil la ramène au littéral %20. Le vrai bug est ailleurs. Donne-lui une URL de base contenant un fragment, https://example.com/page#section, et la chaîne est ajoutée après le fragment : l'analyseur d'URL du navigateur signale alors une partie requête vide et place tous les paramètres dans le hash, où aucun serveur ne les verra jamais. Son analyseur jette aussi le fragment, et une séquence héritée comme %E9 échoue au décodage, est conservée en texte littéral et ressort en %25E9 à la construction suivante.
Trois encodages, une différence visible : %20 ou +. Le mode formulaire du générateur reproduit URLSearchParams octet pour octet sur dix-sept valeurs — mais donne-lui une URL de base avec un fragment et tous les paramètres atterrissent dans le hash, invisibles du serveur.
Deux normes, et les navigateurs appliquent la plus récente
La RFC 3986 est le document le plus ancien et celui que tout le monde cite. Elle répartit les caractères entre non réservés — A-Z, a-z, 0-9 et les quatre signes - . _ ~ — et tout le reste, réservé ou à encoder en pour cent. C'est un modèle propre, et ce n'est pas celui qu'exécute ton navigateur. Les navigateurs appliquent le WHATWG URL Standard, un document vivant qui spécifie l'analyse et la sérialisation au moyen de plusieurs jeux d'encodage nommés, un par partie de l'URL. Les deux s'accordent sur les cas courants et divergent aux marges, et c'est aux marges qu'une URL cesse de fonctionner.
Le standard URL est explicite sur cette relation. Il indique que l'usage de son jeu d'encodage « component » avec l'UTF-8 donne des résultats identiques à encodeURIComponent de JavaScript. Et il définit le jeu application/x-www-form-urlencoded comme le jeu component augmenté de ! ' ( ) ~ — puis dit la même chose à l'envers, dans la phrase qui mérite d'être retenue : ce jeu contient tous les points de code sauf les caractères alphanumériques ASCII et * - . _ . Quatre caractères. Voilà toute la liste sûre d'un envoi de formulaire, et cela explique d'un coup d'œil pourquoi les deux modes divergent exactement sur ! ' ( ) ~ tout en laissant l'astérisque intact.
Le troisième mode du générateur, RFC 3986 stricte, est celui à prendre quand un serveur signe l'URL. Il reprend la sortie d'encodeURIComponent et échappe en plus ! ' ( ) * , en laissant le ~ intact, parce que le tilde est non réservé dans la RFC 3986 et que l'échapper changerait la signature. Les passerelles de paiement, les signatures OAuth 1.0 et certaines API d'entreprise plus anciennes calculent une empreinte sur la chaîne encodée : un encodeur qui laisse une apostrophe telle quelle produit donc une empreinte différente de la leur, et la requête est rejetée sans explication exploitable.
Oui il double l'encodage, et c'est la bonne réponse
Le double encodage est la plainte classique contre les générateurs d'URL, il a donc été testé directement. Saisis %20 dans un champ de valeur et la sortie est %2520. Saisis caf%C3%A9 et tu obtiens caf%25C3%25A9. Saisis un simple signe pour cent et tu obtiens %25. Ce n'est pas un bug, c'est la définition du champ. La case valeur contient la valeur saisie par ton utilisateur, et si cette valeur est réellement la suite de caractères c a f % 2 0, alors %2520 est le seul encodage qui la livrera.
Pour vérifier qu'un générateur est correct sur ce point, il ne faut pas regarder une sortie mais faire un aller-retour. Construis une URL, recolle-la dans le champ d'analyse de l'outil, reconstruis. Six valeurs ont subi ce cycle — une phrase accentuée avec une esperluette, un signe plus, un %20 littéral, un dièse dans une valeur, une valeur vide et un objet JSON — et les six sont revenues identiques à l'octet. Le %2520 collé s'est décodé en %20 littéral, qui s'est réencodé en %2520. Un générateur qui retirerait une couche pour faire propre échouerait précisément ici, en silence, sur la seule valeur qui compte.
Le bug : un fragment dans l'URL de base avale tous les paramètres
Le générateur raccorde sa chaîne à la base en cherchant un point d'interrogation : si la base en a déjà un, il ajoute avec une esperluette, sinon avec un point d'interrogation. Les deux branches ajoutent à la fin. Une URL ne fonctionne pas ainsi. L'ordre des parties est fixe — schéma, autorité, chemin, requête, fragment — et le fragment vient en dernier, si bien que tout ce qui s'écrit après lui lui appartient.
Mets la base à https://example.com/page#section, ajoute q et page, et l'outil affiche https://example.com/page#section?q=caf%C3%A9%20%26%20croissant&page=2. Ça a l'air correct. Passe cette chaîne à l'analyseur d'URL du navigateur et il rend un chemin /page, une partie requête vide, et un hash contenant #section?q=caf%C3%A9%20%26%20croissant&page=2 — tous les paramètres dans le fragment. Un fragment n'est jamais envoyé au serveur. La page se chargera, aucune erreur n'apparaîtra nulle part, et les paramètres n'existeront tout simplement pas du point de vue du serveur. La variante avec une requête déjà présente, https://example.com/page?a=1#frag, raconte la même histoire : la partie requête revient à ?a=1 et les nouveaux paramètres sont dans le hash.
L'analyse a le problème symétrique : elle coupe l'entrée au premier dièse et jette tout ce qui suit. Colle https://example.com/p?a=1#frag et la base revient en https://example.com/p, fragment disparu. Tu ne peux donc pas du tout utiliser l'outil sur une URL qui possède un fragment — il le perd à l'entrée et place mal la requête à la sortie. En attendant un correctif, la parade est de retirer le fragment toi-même, de construire la chaîne, puis de réassembler à la main : la base, puis la requête, puis le dièse, dans cet ordre.
Trois points plus petits à connaître avant de s'y fier
D'abord, une séquence pour cent qu'il ne sait pas décoder est conservée en texte littéral puis réencodée à la sortie. Colle une URL contenant a=%E9 — une séquence Latin-1 sur un octet, comme en émettent encore quantité de systèmes d'avant 2010 — et le décodeur lève une erreur, l'outil la rattrape et garde les trois caractères % E 9 comme valeur. Reconstruis et cela devient a=%25E9, une URL différente de celle que tu as collée. Rien ne te prévient. Le réflexe sûr est de vérifier tout paramètre qui revient en contenant un signe pour cent.
Ensuite, le générateur regroupe les paramètres par clé au lieu de conserver l'ordre de tes lignes. Les lignes a=1, b=2, a=3 ressortent en a=1&a=3&b=2 : les deux lignes a sont rapprochées et b descend. Pour un serveur web ordinaire c'est sans conséquence, presque rien ne se soucie de l'ordre des paramètres. Pour une requête signée, si, parce que la signature porte sur la chaîne exacte. Compare la sortie à l'ordre saisi dès que le destinataire calcule une empreinte sur la requête.
Enfin, l'option de tri des clés trie avec la comparaison sensible à la locale du navigateur, pas par point de code. Avec les clés b, a, B et _x elle produit _x, a, b, B. Un tri par point de code — celui que spécifie tout schéma de signature existant — produit B, _x, a, b. Les deux diffèrent dès que tes clés mêlent majuscules et minuscules ou commencent par un tiret bas, ce que font justement les noms de paramètres d'une API signée. Sers-toi de l'option pour rendre une longue URL lisible ; ne t'en sers pas pour la canoniser.
Deux points qu'il traite bien alors qu'il est facile de se tromper. Ses quatre syntaxes de valeurs répétées encodent toutes les crochets : a[]=1 s'écrit a%5B%5D=1 et a[0]=1 s'écrit a%5B0%5D=1, ce qui est correct au regard de la RFC 3986 où les crochets sont réservés aux littéraux d'hôte IPv6 — et ce que PHP, Rails et Express décodent tous. Et en mode virgule, la virgule entre les valeurs est encodée elle aussi : color=red,blue s'écrit color=red%2Cblue, ce qui est légal et sans danger avec tout serveur qui décode avant de découper.
| Valeur saisie | encodeURIComponent | x-www-form-urlencoded | RFC 3986 stricte |
|---|---|---|---|
| two words | two%20words | two+words | two%20words |
| café & croissant | caf%C3%A9%20%26%20croissant | caf%C3%A9+%26+croissant | caf%C3%A9%20%26%20croissant |
| C'est (chouette) ! | C'est%20(chouette)%20! | C%27est+%28chouette%29+%21 | C%27est%20%28chouette%29%20%21 |
| ~tilde*star | ~tilde*star | %7Etilde*star | ~tilde%2Astar |
| a=b&c#d | a%3Db%26c%23d | a%3Db%26c%23d | a%3Db%26c%23d |
| %20 saisi littéralement | %2520 | %2520 | %2520 |
Questions fréquentes
- Quel encodage choisir si je ne sais pas ce qu'attend le serveur ?
- Prends encodeURIComponent, le mode par défaut. Une espace devient %20, que tout serveur décode correctement, tandis que le + n'est interprété comme une espace que par du code qui sait lire des données de formulaire. C'est toute la raison de préférer %20 dans une URL destinée à être collée dans un e-mail, une messagerie ou un tableur : elle survit à une lecture par quelque chose qui ignore qu'elle vient d'un formulaire. Passe à x-www-form-urlencoded seulement pour reproduire ce qu'enverrait un formulaire de navigateur — par exemple pour comprendre pourquoi un envoi de formulaire diffère de ton URL construite à la main. Passe à la RFC 3986 stricte quand un serveur signe ou hache la chaîne de requête, parce que les échappements supplémentaires de ! ' ( ) * sont ceux que produisent la plupart des bibliothèques de signature.
- Pourquoi mes paramètres ont-ils disparu quand le lien se terminait par #section ?
- Parce qu'ils ont été écrits après le fragment, et que tout ce qui suit un dièse est le fragment. Ce générateur ajoute la chaîne à la fin de ce que tu lui as donné comme base : une base https://example.com/page#section produit donc https://example.com/page#section?q=x, et le navigateur lit toute la fin comme un seul fragment. Un fragment est résolu entièrement côté client ; il ne fait pas partie de la ligne de requête et aucun serveur ne le voit. Répare à la main : prends la base sans son fragment, ajoute la chaîne, puis remets le fragment tout à la fin, ce qui donne https://example.com/page?q=x#section. Si tu recolles cette URL corrigée dans l'outil, il analysera bien la requête mais perdra de nouveau le fragment : garde donc le fragment ailleurs pendant que tu travailles.
- Comment envoyer une liste de valeurs pour le même paramètre ?
- Il n'y a pas de norme, et c'est pourquoi l'outil propose quatre syntaxes. Répéter la clé, color=red&color=blue, c'est ce que produit un formulaire HTML quand plusieurs cases partagent un nom, et ce que renvoie URLSearchParams.getAll ; c'est le choix par défaut le plus sûr. Les crochets, color[]=red&color[]=blue, sont une convention PHP que lisent aussi Rails et plusieurs cadres PHP. Les crochets indexés, color[0]=red, conservent la position et servent là où le serveur reconstruit un tableau ordonné. Une valeur jointe par virgules, color=red,blue, est un seul paramètre d'une seule valeur que le serveur découpe lui-même. Choisis celle que ton serveur documente ; s'il n'en documente aucune, répète la clé. Note que l'outil encode les crochets et la virgule, ce qui est légal et que chacun de ces cadres décode avant d'analyser.
- Est-il prudent de mettre une adresse e-mail ou un numéro de commande dans une chaîne de requête ?
- Techniquement cela fonctionnera, et il faut quand même l'éviter. Une chaîne de requête fait partie de l'URL, et les URL sont écrites dans l'historique du navigateur, dans les journaux d'accès du serveur, dans ceux des proxys et des CDN, et dans l'en-tête Referer envoyé à tout script tiers chargé par la page. Toute donnée personnelle que tu y places est recopiée dans tous ces endroits par des systèmes à qui personne n'a dit qu'elle était personnelle, et elle y reste pour la durée de conservation propre à chacun. Mets sans hésiter des identifiants dans la chaîne — un identifiant de produit, un numéro de page, un nom de campagne — et mets tout ce qui identifie une personne dans le corps d'une requête POST, ou derrière un jeton opaque court que ton propre serveur résout. Idem pour tout ce qui donne un accès : un jeton dans une URL est un jeton dans un fichier de journal.
- Jusqu'où une URL peut-elle aller avant que quelque chose la tronque ?
- Aucune spécification ne fixe de limite ; chaque implémentation fixe la sienne, et celle qui frappe en premier n'est généralement pas le navigateur. La RFC 3986 refuse explicitement d'imposer un maximum et recommande plutôt que tout ce qui manipule des URL sache encaisser des longueurs supérieures à ce qu'il attend. En pratique, les navigateurs acceptent des dizaines de milliers de caractères, tandis que serveurs web et proxys refusent couramment une ligne de requête au-delà d'environ 8 kilo-octets et répondent par un statut 414. Les outils d'analyse et de résultats de recherche tronquent bien plus tôt. Le conseil pratique ne dépend d'aucun chiffre exact : si ta chaîne de requête atteint des milliers de caractères, les données ont leur place dans un corps de requête POST ou derrière un identifiant court, pas dans un lien. Les URL longues cassent aussi au collage dans les clients de messagerie et les applications de discussion, qui les coupent à une colonne et transforment un lien en deux.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Ces chiffres viennent de l'exécution de ces outils sur de vrais fichiers, puis de la compression du résultat, pas d'une promesse d'éditeur. Le poids dépend entièrement du fichier : une feuille de style écrite avec de longs commentaires ne se comprime pas comme une feuille sans commentaires, et tes chiffres ne seront pas les nôtres. Les minifieurs ne se valent pas non plus — deux outils de ce site divergent sur la même entrée — alors traite toute sortie minifiée comme du code neuf, à relire avant mise en ligne. Garde l'original lisible dans le gestionnaire de versions, minifie à la compilation, et vérifie la page dans un navigateur avant de publier.
Sources
- WHATWG — URL Standard — the percent-encode sets: the component set, which the specification states gives identical results to encodeURIComponent when used with UTF-8, and the application/x-www-form-urlencoded set, which contains every code point except the ASCII alphanumerics and * - . _
- IETF — RFC 3986, Uniform Resource Identifier (URI): Generic Syntax — section 2.3 on the unreserved set A-Z a-z 0-9 - . _ ~, section 3.5 on the fragment being the last component, and section 3 on the absence of any maximum length
- Ecma International — ECMA-262, the ECMAScript language specification — encodeURIComponent and the unescapedURIComponentSet it preserves, which is where the exceptions for ! ' ( ) * ~ come from
- WHATWG — HTML Standard, form control infrastructure — how a form's entry list is serialised, and why a submitted form writes a space as + rather than %20
Tu as repéré une erreur dans cet article ?