Skip to content
Terug naar blog
Tutorials

URL-encoderen en -decoderen: developer-gids voor percent encoding

RFC 3986-regels, encodeURI vs encodeURIComponent, UTF-8-bytemapping en codevoorbeelden in JS, Python, Go en Java. Probeer onze gratis online tool.

12 min leestijd

URL-encoderen en -decoderen: developer-gids voor percent encoding

Je volgt een server-log en ziet dit in een query-string: %E4%BD%A0%E5%A5%BD. Corrupte data? Een bug? Geen van beide — het zijn de Chinese karakters 你好, elk omgezet naar drie UTF-8-bytes en vervolgens percent-encoded in een URL-veilig formaat. Elke webdeveloper loopt hier vroeg of laat tegenaan: iets ziet er kapot uit, maar de URL werkt precies zoals bedoeld.

URL-encoderen — formeel percent encoding genoemd — is het mechanisme dat speciale karakters veilig maakt voor URL’s. Deze gids behandelt hoe het werkt op byte-niveau, wanneer je encodeURI gebruikt versus encodeURIComponent, hoe je correct encodeert in vier talen en welke bugs zelfs ervaren developers op het verkeerde been zetten.

Plak een willekeurige URL in onze URL-decoder en -encoder om encoderen en decoderen in realtime te zien terwijl je meeleest.

Wat is URL-encoderen (percent encoding)?

Een URL kan maar een kleine subset van ASCII-karakters bevatten. Letters, cijfers en een handjevol symbolen gaan zonder problemen het internet over. Al het andere — spaties, ampersands, Chinese tekst, emoji — moet worden omgezet naar een formaat dat URL’s kunnen dragen.

Percent encoding vervangt elke onveilige byte door een %-teken gevolgd door twee hexadecimale cijfers. Een spatie wordt %20. Een ampersand wordt %26. De naam komt van dat %-prefix.

De regels staan in RFC 3986, gepubliceerd in 2005 en nog steeds de geldende standaard. Het verving RFC 2396 en scherpte de definitie aan van welke karakters veilig zijn, welke gereserveerd en hoe niet-ASCII-tekst moet worden behandeld.

Korte voorbeelden:

InvoerGeëncodeerdWaarom
hello worldhello%20worldSpatie is niet toegestaan in URL’s
price=10&tax=2price%3D10%26tax%3D2= en & hebben structurele betekenis
%E4%B8%ADNiet-ASCII → UTF-8-bytes → percent-encoded
🚀%F0%9F%9A%80Emoji → 4 UTF-8-bytes → percent-encoded

Welke karakters moeten worden geëncodeerd?

RFC 3986 deelt karakters in drie groepen op.

Ongereserveerde karakters (nooit geëncodeerd)

Deze 66 karakters gaan ongewijzigd door in elk deel van een URL:

A-Z  a-z  0-9  -  .  _  ~

Letters, cijfers, koppelteken, punt, underscore, tilde. Dat is de volledige lijst.

Gereserveerde karakters (contextafhankelijk)

Deze karakters dienen als structurele scheidingstekens in URL’s:

KarakterRol in URL-structuur
:Scheidt schema van authority (https:)
/Scheidt pad-segmenten
?Start de query-string
#Start het fragment
&Scheidt query-parameters
=Scheidt parameter-sleutel van waarde
@Scheidt userinfo van host
+ ! $ ' ( ) * , ; [ ]Diverse gereserveerde rollen

De regel: als een gereserveerd karakter zijn structurele doel dient, laat het met rust. Als het als data verschijnt (binnen een parameterwaarde bijvoorbeeld), encodeer het dan.

Al het andere (altijd geëncodeerd)

Spaties, hoekhaken, accolades, pipes, backslashes en niet-ASCII-karakters (Chinees, Arabisch, emoji) moeten percent-geëncodeerd worden.

Eén complicatie: RFC 3986 codeert spaties als %20, maar HTML-formulierinzendingen gebruiken +. Meer over dit conflict verderop.

Hoe URL-encoderen echt werkt: de UTF-8-pijplijn

Voor ASCII-karakters is encoderen eenvoudig: zoek de bytewaarde op in hex en zet % ervoor. Een spatie (bytewaarde 32, hex 20) wordt %20.

Voor niet-ASCII-tekst bestaat het encoderen uit drie stappen:

Stap 1 — karakter naar Unicode-codepunt. Het karakter é wordt omgezet naar codepunt U+00E9. De emoji 🚀 wordt omgezet naar U+1F680.

Stap 2 — codepunt naar UTF-8-bytes. UTF-8 gebruikt 1 tot 4 bytes afhankelijk van het codepunt-bereik. é (U+00E9) wordt twee bytes: 0xC3 0xA9. De raket-emoji (U+1F680) wordt vier bytes: 0xF0 0x9F 0x9A 0x80.

Stap 3 — elke byte naar %XX. Elke byte uit stap 2 krijgt zijn eigen percent-encoded triplet.

Hier is de volledige pijplijn voor verschillende karaktertypen:

KarakterCodepuntUTF-8-bytesGeëncodeerdGroottefactor
AU+004141A (niet gecodeerd)
spatieU+002020%20
éU+00E9C3 A9%C3%A9
U+4E2DE4 B8 AD%E4%B8%AD
🚀U+1F680F0 9F 9A 80%F0%9F%9A%8012×

Je kunt dit zelf verifiëren in JavaScript:

const char = '中';
const encoded = encodeURIComponent(char);
console.log(encoded); // '%E4%B8%AD'

// Trace the bytes
const bytes = new TextEncoder().encode(char);
console.log([...bytes].map(b => '%' + b.toString(16).toUpperCase()).join(''));
// '%E4%B8%AD' — matches

Deze uitbreiding doet ertoe bij URL-lengtelimieten. Een URL met 20 Chinese karakters voegt 180 karakters percent-geëncodeerde tekst toe.

encodeURI vs encodeURIComponent — de juiste functie kiezen

Deze twee JavaScript-functies worden continu door elkaar gehaald. Ze lijken op elkaar maar encoderen totaal verschillende karaktersets.

encodeURI()encodeURIComponent()
DoelEen volledige URL encoderenÉén component encoderen (parametersleutel of -waarde)
Behoudt: / ? # & = @ + $ ,Geen enkele hiervan
CodeertSpaties, niet-ASCII, wat leestekensAlles behalve A-Z a-z 0-9 - _ . ~ ! ' ( ) *
Gebruik wanneerJe een volledige URL hebt met spaties of Unicode in het padJe query-parameters bouwt op basis van gebruikersinvoer

Een bug die regelmatig in productie belandt:

// ❌ BUG: encodeURI does NOT encode &
const search = 'Tom & Jerry';
const bad = `https://api.example.com/search?q=${encodeURI(search)}`;
// Result: https://api.example.com/search?q=Tom%20&%20Jerry
// The & splits the query string — server sees q=Tom%20 and a separate param %20Jerry

// ✅ FIX: encodeURIComponent encodes & as %26
const good = `https://api.example.com/search?q=${encodeURIComponent(search)}`;
// Result: https://api.example.com/search?q=Tom%20%26%20Jerry

Bij twijfel kies je encodeURIComponent(). Het werkt correct in 95% van de praktijkgevallen waarin je URL’s bouwt.

Probeer beide modi naast elkaar in onze URL-encoder →

URL-encoderen in elke taal

JavaScript (browser en Node.js)

// Encode a parameter value
const value = encodeURIComponent('price >= 100 & currency = €');
// 'price%20%3E%3D%20100%20%26%20currency%20%3D%20%E2%82%AC'

// Decode
const original = decodeURIComponent(value);
// 'price >= 100 & currency = €'

// Modern approach: URLSearchParams handles encoding automatically
const params = new URLSearchParams({ q: 'hello world', lang: '中文' });
console.log(params.toString());
// 'q=hello+world&lang=%E4%B8%AD%E6%96%87'
// Note: URLSearchParams uses + for spaces (form encoding)

Python

from urllib.parse import quote, unquote, urlencode

# Encode a path segment
quote('hello world/file name.txt', safe='/')
# 'hello%20world/file%20name.txt'

# Encode query parameters
urlencode({'q': '你好', 'page': '1'})
# 'q=%E4%BD%A0%E5%A5%BD&page=1'

# quote_plus uses + for spaces (form encoding)
from urllib.parse import quote_plus
quote_plus('hello world')  # 'hello+world'
quote('hello world')       # 'hello%20world'

Go

import "net/url"

// Encode a query value (uses + for spaces)
url.QueryEscape("hello world & more")
// "hello+world+%26+more"

// Encode a path segment (uses %20 for spaces)
url.PathEscape("hello world & more")
// "hello%20world%20&%20more"

// Build a URL safely with url.Values
params := url.Values{}
params.Set("q", "你好世界")
params.Set("page", "1")
fmt.Println(params.Encode())
// "page=1&q=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C"

Java

import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

// Encode (uses + for spaces — Java follows form encoding)
String encoded = URLEncoder.encode("hello world & more", StandardCharsets.UTF_8);
// "hello+world+%26+more"

// For RFC 3986 compliance, replace + with %20
String rfc3986 = encoded.replace("+", "%20");
// "hello%20world%20%26%20more"

// Decode
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
// "hello world & more"

Go en Java gebruiken standaard form encoding (spaties als +). Voor RFC 3986-uitvoer verwerk je het resultaat na door + te vervangen door %20.

Vijf bugs bij URL-encoderen die productie breken

1. Dubbel encoderen (%2520 in plaats van %20)

Je encodeert een string. Een framework encodeert hem opnieuw. De % in %20 wordt %25 en de server ziet letterlijke %20-tekst in plaats van een spatie.

Symptoom: URL’s bevatten %2520, %253D of andere %25xx-patronen.

Diagnose: %25 in een URL betekent dat een %-karakter is gecodeerd, wat meestal wijst op dubbel encoderen.

Oplossing: Decodeer eerst, encodeer daarna één keer. Controleer of de invoer al gecodeerd is voordat je hem encodeert.

// Detect double encoding
function isDoubleEncoded(str) {
  return /%25[0-9A-Fa-f]{2}/.test(str);
}

// Safe encode: decode first, then encode
function safeEncode(str) {
  try { str = decodeURIComponent(str); } catch (e) { /* not encoded, that's fine */ }
  return encodeURIComponent(str);
}

2. + in pad-segmenten

Een developer URL-encodeert een bestandsnaam met een bibliotheek die + uitvoert voor spaties. Het bestand my report.pdf wordt my+report.pdf. De server behandelt + als een letterlijk plus-teken en geeft een 404 terug.

De regel: + betekent alleen spatie in query-strings (na ?). In pad-segmenten is + gewoon +. Gebruik altijd %20 voor spaties in paden.

3. Gebroken OAuth redirect-URI’s

De autorisatie-URL ziet er zo uit:

https://auth.provider.com/authorize?redirect_uri=https://myapp.com/callback?code=abc&state=xyz

De OAuth-server leest redirect_uri=https://myapp.com/callback?code=abc en behandelt state=xyz als een aparte top-level parameter. Authenticatie mislukt.

Oplossing: Encodeer de volledige redirect-URI-waarde:

const redirectUri = 'https://myapp.com/callback?code=abc&state=xyz';
const authUrl = `https://auth.provider.com/authorize?redirect_uri=${encodeURIComponent(redirectUri)}`;
// redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback%3Fcode%3Dabc%26state%3Dxyz

4. Onleesbare niet-ASCII-tekst in logs

Server-logs tonen %E4%BD%A0%E5%A5%BD in plaats van leesbare Chinese karakters. De URL is correct gecodeerd; je log-viewer decodeert de percent-geëncodeerde sequenties alleen niet.

Oplossing: Stuur logs door een decoder, of plak de URL in een URL-decoder om de originele tekst te lezen.

5. Falende API-signing

OAuth 1.0 en AWS Signature V4 vereisen strikte RFC 3986-codering. JavaScripts encodeURIComponent() codeert !, ', (, ) of * niet. Als deze karakters in je signing-invoer voorkomen, klopt de handtekening niet.

Oplossing: Verwerk de uitvoer na:

function rfc3986Encode(str) {
  return encodeURIComponent(str).replace(/[!'()*]/g, c =>
    '%' + c.charCodeAt(0).toString(16).toUpperCase()
  );
}

%20 vs + — het dilemma van spatie-encoderen

Twee standaarden zijn het oneens over hoe je één karakter codeert.

StandaardSpatie wordtWaar het van toepassing is
RFC 3986 (URI-syntaxis)%20Overal in een URL
application/x-www-form-urlencoded+Query-strings uit HTML-formulierinzendingen

De +-conventie is een overblijfsel uit de begintijd van webbrowsers. Als een <form> verstuurt met method="GET", encodeert de browser spaties als + in de query-string. De HTML-specificatie legt dit gedrag vast.

Het probleem: + betekent alleen “spatie” in query-strings. In pad-segmenten is een + een letterlijk plus-teken. Daarom serveert https://example.com/my+file.pdf een bestand met de naam my+file.pdf, niet my file.pdf.

Praktische richtlijn:

  • Gebruik %20 bij het handmatig bouwen van URL’s of het coderen van pad-segmenten. Het werkt overal.
  • Accepteer + bij het parsen van query-strings uit formulierinzendingen — je framework regelt dit waarschijnlijk al.
  • Meng ze niet. Kies één conventie per component en houd je eraan.

URL-encoderen en beveiliging

URL-encoderen is GEEN versleuteling

Percent encoding is een volledig omkeerbare, deterministische transformatie zonder cryptografische eigenschappen. Iedereen kan %48%65%6C%6C%6F binnen milliseconden terug decoderen naar Hello.

Gebruik URL-encoderen niet om gevoelige data te verbergen. Gebruik HTTPS om het hele verzoek te versleutelen. URL’s verschijnen in server-logs, browsergeschiedenis en Referer-headers, dus gevoelige informatie hoort in request-bodies, niet in URL’s.

Open redirect-aanvallen

Aanvallers gebruiken geëncodeerde URL’s om naïeve validatie te omzeilen. Een redirect-parameter met %2F%2Fevil.com decodeert naar //evil.com, wat browsers behandelen als een protocol-relatieve URL die naar het domein van de aanvaller wijst.

Verdediging: Valideer de gedecodeerde URL, niet de geëncodeerde vorm. Gebruik allowlists voor redirect-domeinen.

Dubbel-encoderen-exploits

Een WAF controleert inkomende URL’s op <script>-tags. Een aanvaller stuurt %253Cscript%253E. De WAF ziet percent-encoded tekst en laat het door. De applicatie decodeert één keer naar %3Cscript%3E, een tweede decodering produceert <script>, wat het filter omzeilt.

Verdediging: Normaliseer alle invoer (volledig decoderen) voordat je beveiligingscontroles toepast. Vertrouw niet op één enkele decodeer-stap.

Voor meer over de basis van webbeveiliging, zie onze Web Security Essentials-gids.

URL-lengtelimieten en wanneer encoderen duur wordt

De HTTP-specificatie stelt geen maximum URL-lengte, maar elke laag van de stack legt praktische limieten op.

LaagLimiet
Algemene aanbeveling2.000 karakters
Chrome, Firefox~2 MB (maar servers weigeren ruim daarvoor al)
Apache (standaard)8.190 bytes
Nginx (standaard)8.192 bytes
IIS16.384 bytes (query-string)
CDN’s, proxy’sVarieert — vaak 4.096-8.192 bytes

Percent encoding maakt URL’s langer. Eén Chinees karakter groeit van 1 karakter naar 9 (%E4%B8%AD). Een emoji breidt uit naar 12. Tweehonderd Chinese karakters in alleen al een query-string produceren 1.800 karakters percent-encoded tekst.

Als je tegen de limiet aanloopt: Verplaats data van query-parameters naar een POST-request-body. Voor zoekinterfaces werkt een POST-endpoint dat JSON accepteert goed.

FAQ

Wat is URL-encoderen en waarom hebben developers het nodig?

URL-encoderen (percent encoding) zet karakters die niet in URL’s zijn toegestaan om in %XX hex-sequenties. URL’s ondersteunen slechts 66 ongereserveerde ASCII-karakters. Spaties, ampersands, Unicode-tekst en de meeste leestekens moeten worden geëncodeerd of ze breken de URL-structuur.

Wat is het verschil tussen encodeURI en encodeURIComponent?

encodeURI() codeert een volledige URL en behoudt daarbij structurele karakters zoals ://, /, ? en &. encodeURIComponent() codeert alles behalve A-Z a-z 0-9 - _ . ~ ! ' ( ) *. Gebruik encodeURIComponent() voor query-parameterwaarden. Gebruik encodeURI() alleen als je een complete URL hebt en spaties of niet-ASCII-karakters wilt corrigeren zonder de structuur te breken.

Waarom verschijnt %20 soms als + in URL’s?

Beide representeren een spatie, maar ze komen uit verschillende standaarden. %20 volgt RFC 3986 en werkt overal in een URL. + volgt de HTML form-encoding-specificatie en werkt alleen in query-strings. In pad-segmenten is + een letterlijk plus-teken. Gebruik %20 bij twijfel.

Hoe URL-encodeer ik tekst in Python, JavaScript, Go en Java?

JavaScript: encodeURIComponent('hello world')hello%20world. Python: urllib.parse.quote('hello world')hello%20world. Go: url.QueryEscape("hello world")hello+world. Java: URLEncoder.encode("hello world", UTF_8)hello+world. Go en Java gebruiken standaard form encoding (spatie als +) — vervang + door %20 voor RFC 3986-uitvoer.

Kun je URL-encoderen gebruiken voor beveiliging of versleuteling?

Nee. URL-encoderen is volledig omkeerbaar zonder sleutel. Het biedt geen enkele vertrouwelijkheid. Bescherm gevoelige data met HTTPS, niet met percent encoding. URL’s verschijnen in server-logs, browsergeschiedenis en Referer-headers, dus gevoelige data hoort in request-bodies.

Wat is dubbel encoderen en hoe los ik het op?

Dubbel encoderen gebeurt als een al geëncodeerde string opnieuw geëncodeerd wordt. De % in %20 wordt gecodeerd als %25, wat %2520 oplevert. Servers zien letterlijke %20-tekst in plaats van een spatie. Los het op door de invoer eerst te decoderen en daarna één keer te encoderen. Het patroon %25 gevolgd door twee hex-cijfers is het verklikkende teken.

Wat is de maximum URL-lengte?

Er bestaat geen officieel maximum in de HTTP-specificatie. 2.000 karakters is de veilige limiet voor brede compatibiliteit. Apache staat standaard op 8.190 bytes, Nginx op 8.192 bytes. Niet-ASCII-karakters breiden 3-12x uit bij percent encoding, dus geïnternationaliseerde URL’s lopen sneller tegen limieten aan. Voor grote payloads stap je over op POST.

Gerelateerde artikelen

Alle artikelen bekijken