Eine URL mit Parametern bauen, die ein Kopieren und Einfügen übersteht
Veröffentlicht am 11.8.2026 · 11 Min. Lesezeit · Entwickler-Tools
Daniel Okonkwo — Front-end-Entwickler und Tech-Redakteur bei Allin
Web-Performance · Dateiformate
Anhand von 4 Quellen geprüft
Ein Query-String bricht, wenn ein Zeichen, das für die URL etwas bedeutet — ein Leerzeichen, ein Und-Zeichen, ein Gleichheitszeichen, eine Raute —, innerhalb eines Werts es selbst bleibt. Prozentkodierung behebt das, aber es gibt drei Kodierungen, sie widersprechen sich, und der Widerspruch zeigt sich am Leerzeichen. encodeURIComponent schreibt ein Leerzeichen als %20 und lässt ! ' ( ) * ~ unangetastet. Der application/x-www-form-urlencoded-Serialisierer, den URLSearchParams und jedes HTML-Formular verwenden, schreibt ein Leerzeichen als + und kodiert ! ' ( ) ~, lässt aber * stehen. Striktes RFC 3986 kodiert alles außerhalb von A-Z a-z 0-9 - . _ ~, escapt also auch * und behält ~. Dieser Generator bietet alle drei an, und sein Formularmodus wurde gegen das URLSearchParams von Node an siebzehn Werten geprüft, darunter ein Emoji, ein Zeilenumbruch und eine akzentuierte Zeichenkette: jedes Mal byteidentisch. Er kodiert tatsächlich doppelt: Gib %20 als Wert ein, und du bekommst %2520. Das ist richtig — du gibst einen rohen Wert ein, keinen vorkodierten —, und der eigene Parser des Werkzeugs führt ihn auf das Literal %20 zurück. Der eigentliche Fehler liegt woanders. Gib ihm eine Basis-URL mit einem Fragment, https://example.com/page#section, und der Query-String wird hinter das Fragment gehängt: Der URL-Parser des Browsers meldet dann einen leeren Query-Teil und steckt sämtliche Parameter in den Hash, wo kein Server sie je zu sehen bekommt. Sein Parser wirft außerdem das Fragment weg, und eine altgediente Escape-Sequenz wie %E9 lässt sich nicht dekodieren, bleibt als wörtlicher Text stehen und kommt beim nächsten Bauen als %25E9 zurück.
Drei Kodierungen, ein sichtbarer Unterschied: %20 oder +. Der Formularmodus des Generators entspricht URLSearchParams bei siebzehn Werten byteweise — aber gib ihm eine Basis-URL mit Fragment, und jeder Parameter landet im Hash, wo kein Server ihn sieht.
Zwei Standards, und Browser setzen den neueren um
RFC 3986 ist das ältere Dokument und dasjenige, das alle zitieren. Es teilt Zeichen in unreserved — A-Z, a-z, 0-9 und die vier Zeichen - . _ ~ — und alles Übrige, das reserviert ist oder prozentkodiert werden muss. Das ist ein sauberes Modell, und es ist nicht das, was dein Browser ausführt. Browser setzen den WHATWG URL Standard um, ein lebendes Dokument, das Parsen und Serialisieren über mehrere benannte Prozentkodier-Mengen beschreibt, eine je URL-Teil. Beide stimmen in den üblichen Fällen überein und weichen an den Rändern ab — und an den Rändern hört eine URL auf zu funktionieren.
Der URL-Standard ist über dieses Verhältnis eindeutig. Er sagt, dass die Verwendung seiner component-Prozentkodier-Menge mit UTF-8 identische Ergebnisse wie JavaScripts encodeURIComponent liefert. Und er definiert die application/x-www-form-urlencoded-Menge als die component-Menge plus ! ' ( ) ~ — und sagt dasselbe dann umgekehrt, in dem Satz, den man sich merken sollte: Diese Menge enthält alle Codepunkte außer den ASCII-Alphanumerischen und * - . _ . Vier Zeichen. Das ist die vollständige sichere Liste einer Formularübermittlung, und sie erklärt auf einen Blick, warum die beiden Modi genau bei ! ' ( ) ~ auseinandergehen, während beide das Sternchen in Ruhe lassen.
Der dritte Modus des Generators, striktes RFC 3986, ist der, zu dem man greift, wenn ein Server die URL signiert. Er nimmt die Ausgabe von encodeURIComponent und escapt zusätzlich ! ' ( ) * , lässt aber ~ stehen, denn die Tilde ist in RFC 3986 unreserved, und sie zu escapen würde die Signatur ändern. Zahlungs-Gateways, OAuth-1.0-Signaturen und manche älteren Unternehmens-APIs berechnen einen Hash über die kodierte Zeichenkette; ein Encoder, der ein Apostroph stehen lässt, erzeugt also einen anderen Hash als deren, und die Anfrage wird ohne verwertbare Erklärung abgelehnt.
Ja, er kodiert doppelt — und das ist richtig so
Doppelkodierung ist die klassische Beschwerde über URL-Generatoren, also wurde sie direkt geprüft. Gib %20 in ein Wertfeld ein, und die Ausgabe ist %2520. Gib caf%C3%A9 ein, und du bekommst caf%25C3%25A9. Gib ein einzelnes Prozentzeichen ein, und du bekommst %25. Das ist kein Fehler, das ist die Definition des Felds. Das Wertfeld enthält den Wert, den dein Nutzer getippt hat, und wenn dieser Wert tatsächlich die Zeichenfolge c a f % 2 0 ist, dann ist %2520 die einzige Kodierung, die sie ausliefert.
Um zu prüfen, ob ein Generator das richtig macht, schaut man nicht auf eine Ausgabe, sondern macht einen Rundlauf. Baue eine URL, füge sie in das Zerlegen-Feld des Werkzeugs zurück und baue sie erneut. Sechs Werte durchliefen diesen Zyklus — eine akzentuierte Wendung mit Und-Zeichen, ein Pluszeichen, ein wörtliches %20, eine Raute in einem Wert, ein leerer Wert und ein JSON-Objekt — und alle sechs kamen byteidentisch zurück. Das eingefügte %2520 dekodierte zum Literal %20, das wieder zu %2520 kodierte. Ein Generator, der eine Schicht abzöge, um aufgeräumt auszusehen, scheiterte genau hier, still, bei dem einen Wert, auf den es ankommt.
Der Fehler: ein Fragment in der Basis-URL verschluckt jeden Parameter
Der Generator hängt seinen Query-String an die Basis, indem er nach einem Fragezeichen sucht: Hat die Basis bereits eines, hängt er mit einem Und-Zeichen an, sonst mit einem Fragezeichen. Beide Zweige hängen am Ende an. So funktioniert eine URL nicht. Die Reihenfolge der Teile liegt fest — Schema, Autorität, Pfad, Query, Fragment — und das Fragment kommt zuletzt, also gehört alles, was danach geschrieben wird, zu ihm.
Setze die Basis auf https://example.com/page#section, füge q und page hinzu, und das Werkzeug gibt https://example.com/page#section?q=caf%C3%A9%20%26%20croissant&page=2 aus. Sieht in Ordnung aus. Gib diese Zeichenkette dem URL-Parser des Browsers, und er meldet einen Pfad /page, einen leeren Query-Teil und einen Hash mit #section?q=caf%C3%A9%20%26%20croissant&page=2 — sämtliche Parameter im Fragment. Ein Fragment wird nie an den Server geschickt. Die Seite lädt, nirgends erscheint ein Fehler, und die Parameter existieren aus Sicht des Servers schlicht nicht. Die Variante mit bereits vorhandenem Query-Teil, https://example.com/page?a=1#frag, erzählt dasselbe: Der Query-Teil kommt als ?a=1 zurück, und die neuen Parameter stecken im Hash.
Das Zerlegen hat das spiegelbildliche Problem: Es teilt die Eingabe an der ersten Raute und wirft alles danach weg. Füge https://example.com/p?a=1#frag ein, und die Basis kommt als https://example.com/p zurück, Fragment weg. Du kannst das Werkzeug also überhaupt nicht auf einer URL mit Fragment verwenden — es verliert es beim Hineingehen und setzt den Query-Teil beim Herausgehen falsch. Bis das behoben ist, lautet der Behelf: Entferne das Fragment selbst, baue den Query-String und setze von Hand wieder zusammen — Basis, dann Query, dann Raute, in dieser Reihenfolge.
Drei kleinere Dinge, die man wissen sollte, bevor man sich darauf verlässt
Erstens: Eine Prozent-Escape-Sequenz, die er nicht dekodieren kann, bleibt als wörtlicher Text stehen und wird beim Hinausgehen erneut kodiert. Füge eine URL mit a=%E9 ein — eine Ein-Byte-Latin-1-Sequenz, wie sie sehr viele Systeme von vor 2010 noch ausgeben —, und der Dekoder wirft einen Fehler, das Werkzeug fängt ihn ab und behält die drei Zeichen % E 9 als Wert. Baue erneut, und daraus wird a=%25E9, eine andere URL als die eingefügte. Nichts warnt dich. Der sichere Zug ist, jeden Parameter zu prüfen, der mit einem Prozentzeichen zurückkommt.
Zweitens gruppiert der Generator Parameter nach Schlüssel, statt deine Zeilenreihenfolge zu bewahren. Die Zeilen a=1, b=2, a=3 kommen als a=1&a=3&b=2 heraus: Die beiden a-Zeilen werden zusammengezogen und b rutscht nach unten. Für einen gewöhnlichen Webserver ist das harmlos, da sich fast nichts um die Parameterreihenfolge schert. Für eine signierte Anfrage nicht, denn die Signatur wird über die exakte Zeichenkette gebildet. Vergleiche die Ausgabe mit der eingegebenen Reihenfolge, sobald die Gegenstelle den Query-String hasht.
Drittens sortiert die Option „Schlüssel sortieren“ mit dem gebietsschema-abhängigen Vergleich des Browsers, nicht nach Codepunkt. Bei den Schlüsseln b, a, B und _x liefert sie _x, a, b, B. Eine Codepunkt-Sortierung — die jedes existierende Signaturschema vorschreibt — liefert B, _x, a, b. Die beiden weichen ab, sobald deine Schlüssel Groß- und Kleinschreibung mischen oder mit einem Unterstrich beginnen, und genau das tun die Parameternamen einer signierten API gern. Nutze die Option, um eine lange URL lesbar zu machen; nutze sie nicht, um sie zu kanonisieren.
Zwei Dinge, die er richtig macht und die man leicht falsch macht. Seine vier Syntaxen für wiederholte Werte kodieren allesamt die eckigen Klammern: a[]=1 wird als a%5B%5D=1 geschrieben und a[0]=1 als a%5B0%5D=1, was nach RFC 3986 korrekt ist, wo eckige Klammern für IPv6-Host-Literale reserviert sind — und was PHP, Rails und Express alle dekodieren. Und im Komma-Modus wird auch das Komma zwischen den Werten kodiert: color=red,blue wird als color=red%2Cblue geschrieben — zulässig und unbedenklich bei jedem Server, der vor dem Aufteilen dekodiert.
| Eingegebener Wert | encodeURIComponent | x-www-form-urlencoded | Striktes RFC 3986 |
|---|---|---|---|
| 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 wörtlich eingegeben | %2520 | %2520 | %2520 |
Häufige Fragen
- Welche Kodierung wähle ich, wenn ich nicht weiß, was der Server erwartet?
- Nimm encodeURIComponent, den Standard. Ein Leerzeichen wird zu %20, was jeder Server korrekt dekodiert, während ein + nur von Code als Leerzeichen gelesen wird, der weiß, dass er Formulardaten liest. Diese Asymmetrie ist der ganze Grund, %20 in einer URL zu bevorzugen, die du in eine E-Mail, eine Chatnachricht oder eine Tabellenkalkulation einfügen wirst: Sie übersteht das Gelesenwerden durch etwas, das nicht weiß, dass sie aus einem Formular stammt. Wechsle nur dann zu x-www-form-urlencoded, wenn du nachbildest, was ein Browserformular senden würde — etwa beim Debuggen, warum eine Formularübermittlung von deiner handgebauten URL abweicht. Wechsle zu striktem RFC 3986, wenn ein Server den Query-String signiert oder hasht, denn die zusätzlichen Escapes für ! ' ( ) * sind das, was die meisten Signaturbibliotheken erzeugen.
- Warum sind meine Parameter verschwunden, als der Link auf #section endete?
- Weil sie hinter das Fragment geschrieben wurden, und alles nach einer Raute ist das Fragment. Dieser Generator hängt den Query-String ans Ende dessen, was du ihm als Basis gegeben hast; eine Basis https://example.com/page#section ergibt also https://example.com/page#section?q=x, und der Browser liest den ganzen Schwanz als ein Fragment. Ein Fragment wird vollständig auf dem Client aufgelöst; es ist nicht Teil der Anfragezeile, und kein Server bekommt es je zu sehen. Repariere es von Hand: Nimm die Basis ohne ihr Fragment, hänge den Query-String an, und setze das Fragment ganz ans Ende, was https://example.com/page?q=x#section ergibt. Fügst du diese korrigierte URL zurück in das Werkzeug, zerlegt es den Query-String richtig, verliert aber das Fragment erneut — bewahre es also während der Arbeit anderswo auf.
- Wie schicke ich eine Liste von Werten für denselben Parameter?
- Es gibt keinen Standard, deshalb bietet das Werkzeug vier Syntaxen an. Den Schlüssel zu wiederholen, color=red&color=blue, ist das, was ein HTML-Formular erzeugt, wenn mehrere Kästchen einen Namen teilen, und was URLSearchParams.getAll zurückgibt; es ist die sicherste Voreinstellung. Klammern, color[]=red&color[]=blue, sind eine PHP-Konvention, die auch Rails und mehrere PHP-Frameworks lesen. Indizierte Klammern, color[0]=red, bewahren die Position und werden dort verwendet, wo der Server ein geordnetes Array wiederaufbaut. Ein kommagetrennter Wert, color=red,blue, ist ein Parameter mit einem Wert, den der Server selbst zerlegt. Nimm, was dein Server dokumentiert; dokumentiert er nichts, wiederhole den Schlüssel. Beachte, dass das Werkzeug die Klammern und das Komma prozentkodiert — zulässig, und jedes dieser Frameworks dekodiert es vor dem Parsen.
- Ist es sicher, eine E-Mail-Adresse oder eine Bestellnummer in einen Query-String zu setzen?
- Technisch funktioniert es, und du solltest es trotzdem vermeiden. Ein Query-String ist Teil der URL, und URLs werden in den Browserverlauf geschrieben, in Server-Zugriffsprotokolle, in Proxy- und CDN-Protokolle und in den Referer-Header, der an jedes von der Seite geladene Drittanbieterskript geht. Jedes personenbezogene Datum, das du dort ablegst, wird von Systemen an all diese Orte kopiert, denen niemand gesagt hat, dass es personenbezogen ist, und es bleibt dort für die jeweilige Aufbewahrungsfrist. Setze ruhig Kennungen in den Query-String — eine Produkt-ID, eine Seitenzahl, einen Kampagnennamen — und setze alles, was eine Person identifiziert, in den Body einer POST-Anfrage oder hinter ein kurzes, undurchsichtiges Token, das dein eigener Server auflöst. Dasselbe gilt für alles, was Zugriff gewährt: Ein Token in einer URL ist ein Token in einer Logdatei.
- Wie lang darf eine URL sein, bevor irgendetwas sie abschneidet?
- Keine Spezifikation setzt eine Grenze; jede Implementierung setzt ihre eigene, und die, die zuerst zubeißt, ist meist nicht der Browser. RFC 3986 lehnt es ausdrücklich ab, ein Maximum vorzuschreiben, und rät stattdessen, dass alles, was URLs verarbeitet, mit größeren Längen zurechtkommen sollte als erwartet. In der Praxis akzeptieren Browser Zehntausende Zeichen, während Webserver und Proxys eine Anfragezeile über etwa 8 Kilobyte häufig ablehnen und mit Status 414 antworten. Analyse- und Suchergebnis-Werkzeuge schneiden weit früher ab. Der praktische Rat hängt von keiner exakten Zahl ab: Läuft dein Query-String in die Tausende von Zeichen, gehören die Daten in einen POST-Body oder hinter eine kurze Kennung, nicht in einen Link. Lange URLs brechen außerdem beim Einfügen in E-Mail-Programme und Chat-Apps, die sie an einer Spalte umbrechen und aus einem Link zwei machen.
Artikel, die dich interessieren könnten
Alle Ratgeber →Ähnliche Tools
Diese Zahlen stammen daraus, dass diese Werkzeuge auf echte Dateien angewendet und die Ergebnisse komprimiert wurden — nicht aus einem Herstellerversprechen. Das Gewicht hängt vollständig von der Datei ab: Ein Stylesheet mit langen Kommentaren komprimiert anders als eines ohne, und deine Zahlen werden nicht unsere sein. Auch Minifier sind nicht gleichwertig — zwei auf dieser Seite widersprechen sich bei derselben Eingabe —, also behandle jede minifizierte Ausgabe als neuen Code, der vor dem Ausliefern angesehen werden muss. Behalte das lesbare Original in der Versionsverwaltung, minifiziere beim Build und prüfe die Seite im Browser, bevor du veröffentlichst.
Quellen
- 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
Hast du einen Fehler in diesem Artikel entdeckt?