Skip to content
Kembali ke Blog
Tutorial

URL Encoding & Decoding: Panduan Praktis Percent Encoding

Pahami URL encoding dari level byte: RFC 3986, encodeURI vs encodeURIComponent, pipeline UTF-8, plus contoh kode JS, Python, Go & Java. Coba tool online gratis kami.

12 menit membaca

URL Encoding & Decoding: Panduan Percent Encoding yang Developer Butuhkan

Anda buka log server, lalu menemukan ini di query string: %E4%BD%A0%E5%A5%BD. Data rusak? Bug? Bukan — itu karakter Cina 你好. Tiap karakter jadi tiga byte UTF-8, lalu percent-encoded supaya aman masuk URL. Kalau Anda web developer, pasti pernah kena: tampilan URL terlihat rusak, padahal justru berfungsi sesuai desain.

URL encoding — nama formalnya percent encoding — membuat karakter spesial aman untuk URL. Artikel ini membedah cara kerjanya sampai ke level byte, kapan pakai encodeURI versus encodeURIComponent, encoding yang benar di empat bahasa pemrograman, dan bug-bug klasik yang masih menjebak developer berpengalaman.

Tempel URL apa pun ke URL Decoder & Encoder kami untuk melihat encoding dan decoding secara real time sambil mengikuti panduan ini.

Apa Itu URL Encoding (Percent Encoding)?

URL cuma bisa memuat subset kecil dari ASCII. Huruf, angka, dan beberapa simbol lolos tanpa masalah. Sisanya — spasi, ampersand, teks Cina, emoji — harus dikonversi ke format yang URL bisa bawa.

Caranya: ganti tiap byte yang tidak aman dengan % plus dua digit hex. Spasi jadi %20. Ampersand jadi %26. Nama “percent encoding” datang dari awalan % ini.

Spesifikasinya ada di RFC 3986, terbit 2005 dan masih berlaku sampai sekarang. RFC ini menggantikan RFC 2396 — memperketat definisi karakter mana yang aman, mana yang reserved, dan bagaimana menangani teks non-ASCII.

Contoh singkat:

InputEncodedAlasan
hello worldhello%20worldSpasi tidak diizinkan dalam URL
price=10&tax=2price%3D10%26tax%3D2= dan & punya makna struktural
%E4%B8%ADNon-ASCII → byte UTF-8 → percent-encoded
🚀%F0%9F%9A%80Emoji → 4 byte UTF-8 → percent-encoded

Karakter Mana yang Perlu Di-encode?

RFC 3986 membagi karakter jadi tiga kelompok. Paham tiga kelompok ini bisa menghemat jam-jam debugging.

Karakter Unreserved (Tidak Pernah Di-encode)

66 karakter ini boleh langsung masuk di bagian mana pun dari URL:

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

Itu saja. Huruf, angka, hyphen, titik, underscore, tilde. Tidak ada yang lain.

Karakter Reserved (Tergantung Konteks)

Karakter-karakter ini jadi delimiter struktural dalam URL:

KarakterPeran dalam struktur URL
:Memisahkan scheme dari authority (https:)
/Memisahkan segmen path
?Memulai query string
#Memulai fragment
&Memisahkan parameter query
=Memisahkan key dari value parameter
@Memisahkan userinfo dari host
+ ! $ ' ( ) * , ; [ ]Berbagai peran reserved lainnya

Aturan simpelnya: kalau karakter reserved sedang menjalankan fungsi strukturalnya, biarkan. Kalau muncul sebagai data (misal di dalam value parameter), encode.

Sisanya (Selalu Di-encode)

Spasi, angle bracket, kurung kurawal, pipe, backslash, karakter non-ASCII (Cina, Arab, emoji) — semua wajib percent-encode sebelum masuk URL.

Spasi perlu perhatian ekstra: RFC 3986 encode jadi %20, tapi form HTML pakai +. Soal konflik ini dibahas di bagian bawah.

Cara Kerja URL Encoding: Pipeline UTF-8

Untuk ASCII, prosesnya langsung: cari nilai byte dalam hex, tempel % di depan. Spasi (byte 32, hex 20) jadi %20.

Teks non-ASCII butuh tiga langkah:

Langkah 1 — Karakter ke code point Unicode. é punya code point U+00E9. Emoji 🚀 ada di U+1F680.

Langkah 2 — Code point ke byte UTF-8. UTF-8 pakai 1 sampai 4 byte tergantung rentang code point. é (U+00E9) jadi dua byte: 0xC3 0xA9. Roket (U+1F680) jadi empat byte: 0xF0 0x9F 0x9A 0x80.

Langkah 3 — Tiap byte jadi %XX. Masing-masing byte dari langkah 2 dapat triplet percent-encoded sendiri.

Berikut pipeline lengkap untuk beberapa tipe karakter:

KarakterCode PointByte UTF-8EncodedPengali ukuran
AU+004141A (tidak di-encode)
spasiU+002020%20
éU+00E9C3 A9%C3%A9
U+4E2DE4 B8 AD%E4%B8%AD
🚀U+1F680F0 9F 9A 80%F0%9F%9A%8012×

Verifikasi sendiri di 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

Ekspansi ini relevan saat bicara batas panjang URL. Cuma 20 karakter Cina sudah menambah 180 karakter percent-encoded.

encodeURI vs encodeURIComponent — Memilih Fungsi yang Tepat

Ini kesalahan URL encoding paling umum di JavaScript. Nama mirip, tapi set karakter yang mereka encode sangat berbeda.

encodeURI()encodeURIComponent()
TujuanMeng-encode URL lengkapMeng-encode satu komponen (key atau value parameter)
Mempertahankan: / ? # & = @ + $ ,Tidak satu pun dari karakter di atas
Meng-encodeSpasi, non-ASCII, beberapa tanda bacaSemua kecuali A-Z a-z 0-9 - _ . ~ ! ' ( ) *
Gunakan ketikaAnda punya URL lengkap dengan spasi atau Unicode di pathAnda membangun query parameter dari input pengguna

Bug ini masuk production lebih sering dari yang developer mau akui:

// ❌ 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

Ragu pilih yang mana? Pakai encodeURIComponent(). Benar untuk 95% kasus pembuatan URL di dunia nyata.

Coba kedua mode secara berdampingan di tool URL Encoder kami →

URL Encoding di Setiap Bahasa

JavaScript (Browser & 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 dan Java default ke form encoding (spasi jadi +). Butuh output RFC 3986? Ganti + dengan %20 setelah encoding.

Lima Bug URL Encoding yang Merusak Production

1. Double Encoding (%2520 Alih-alih %20)

Anda encode string, lalu framework encode lagi. % dalam %20 ikut ter-encode jadi %25. Server sekarang lihat literal %20, bukan spasi.

Gejala: URL berisi %2520, %253D, atau pola %25xx lainnya.

Diagnosis: Ada %25 di URL? Hampir pasti double encoding — karakter % sendiri ikut ter-encode.

Solusi: Decode dulu, baru encode sekali. Jangan encode string tanpa cek apakah sudah ter-encode.

// 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. + di Segmen Path

Developer encode nama file pakai library yang menghasilkan + untuk spasi. my report.pdf jadi my+report.pdf. Server baca + sebagai plus literal — 404.

Intinya: + artinya spasi cuma di query string (setelah ?). Di path, + ya tetap +. Selalu pakai %20 untuk spasi di path.

3. Redirect URI OAuth yang Rusak

URL otorisasi kayak gini:

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

Server OAuth baca redirect_uri=https://myapp.com/callback?code=abc dan anggap state=xyz sebagai parameter top-level terpisah. Auth gagal.

Solusi: Encode seluruh value redirect URI:

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. Teks Non-ASCII Rusak di Log

Log server menampilkan %E4%BD%A0%E5%A5%BD alih-alih karakter Cina yang bisa dibaca. Ini bukan bug — URL-nya memang ter-encode benar. Yang salah: log viewer Anda tidak men-decode sequence percent-encoded.

Solusi: Alirkan log melalui decoder, atau tempel URL ke URL Decoder untuk membaca teks aslinya.

5. Kegagalan Signing API

OAuth 1.0 dan AWS Signature V4 butuh encoding RFC 3986 yang ketat. Masalahnya, encodeURIComponent() tidak encode !, ', (, ), atau *. Kalau karakter-karakter ini ada di input signing, signature pasti tidak cocok.

Solusi: Tambah post-processing:

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

%20 vs + — Dilema Encoding Spasi

Dua standar, satu karakter, kebingungan tanpa akhir.

StandarSpasi menjadiBerlaku di mana
RFC 3986 (sintaks URI)%20Di mana pun dalam URL
application/x-www-form-urlencoded+Query string dari pengiriman form HTML

Konvensi + warisan dari awal mula web. Saat browser kirim <form> HTML dengan method="GET", spasi di-encode jadi + di query string. Perilaku ini sudah baked in di spesifikasi HTML — tidak akan hilang.

Tapi hati-hati: + cuma berarti “spasi” di query string. Di path, + ya tanda plus literal. Makanya https://example.com/my+file.pdf menyajikan file bernama my+file.pdf, bukan my file.pdf.

Panduan praktis:

  • Pakai %20 saat bangun URL manual atau encode path segment. Aman di mana saja.
  • Terima + waktu parsing query string dari form — framework Anda kemungkinan sudah handle ini.
  • Jangan campur. Satu konvensi per komponen, konsisten.

URL Encoding dan Keamanan

URL Encoding BUKAN Enkripsi

Percent encoding itu transformasi reversible yang deterministik. Tidak ada key, tidak ada secret, nol keamanan. Siapa pun bisa decode %48%65%6C%6C%6F balik jadi Hello dalam milidetik.

Jangan pakai URL encoding untuk sembunyikan data sensitif. Pakai HTTPS untuk enkripsi seluruh request. Ingat juga: URL muncul di log server, riwayat browser, dan header Referer — data sensitif sebaiknya masuk body request, bukan URL.

Serangan Open Redirect

Penyerang craft URL ter-encode untuk lolos dari validasi sederhana. Parameter redirect berisi %2F%2Fevil.com akan ter-decode jadi //evil.com — browser menafsirkannya sebagai URL protocol-relative ke domain penyerang.

Pertahanan: Validasi URL yang sudah di-decode, bukan bentuk ter-encode. Pakai allowlist untuk domain redirect.

Eksploitasi Double Encoding

WAF cek URL masuk untuk tag <script>. Penyerang kirim %253Cscript%253E — WAF lihat teks percent-encoded, lolos. Aplikasi decode sekali jadi %3Cscript%3E, decode kedua menghasilkan <script>. Filter terlewat.

Pertahanan: Normalisasi semua input (decode sepenuhnya) sebelum jalankan security check. Jangan andalkan satu kali decode.

Mau pelajari lebih dalam soal keamanan web? Baca panduan Esensial Keamanan Web kami.

Batas Panjang URL dan Kapan Encoding Menjadi Mahal

Spesifikasi HTTP tidak definisikan panjang URL maksimum. Tapi tiap lapisan stack punya batas praktis masing-masing.

LapisanBatas
Rekomendasi umum2.000 karakter
Chrome, Firefox~2 MB (tapi server menolak jauh sebelum ini)
Apache (default)8.190 byte
Nginx (default)8.192 byte
IIS16.384 byte (query string)
CDN, proxyBervariasi — sering 4.096-8.192 byte

Percent encoding bikin URL makin panjang. Satu karakter Cina mengembang dari 1 jadi 9 karakter (%E4%B8%AD). Emoji jadi 12. URL dengan 200 karakter Cina di query string sudah 1.800 karakter percent-encoded — belum termasuk base URL.

Kalau mentok batas: Pindahkan data dari query parameter ke body request via POST. Untuk fitur pencarian, buat endpoint POST yang terima body JSON berisi kriteria pencarian.

FAQ

Apa itu URL encoding dan mengapa developer membutuhkannya?

URL encoding (percent encoding) mengonversi karakter yang tidak boleh ada di URL jadi sequence hex %XX. URL cuma support 66 karakter ASCII unreserved — spasi, ampersand, teks Unicode, dan kebanyakan tanda baca harus di-encode supaya tidak merusak struktur URL atau bikin server salah interpretasi.

Apa perbedaan antara encodeURI dan encodeURIComponent?

encodeURI() encode URL lengkap tapi pertahankan karakter struktural (://, /, ?, &). encodeURIComponent() encode semuanya kecuali A-Z a-z 0-9 - _ . ~ ! ' ( ) *. Pakai encodeURIComponent() untuk value query parameter. encodeURI() cuma cocok kalau Anda punya URL lengkap dan mau perbaiki spasi atau karakter non-ASCII tanpa merusak struktur.

Mengapa %20 terkadang muncul sebagai + di URL?

Keduanya representasi spasi, tapi dari standar berbeda. %20 ikut RFC 3986, berfungsi di semua komponen URL. + ikut spesifikasi form encoding HTML, cuma valid di query string. Di path, + tetap tanda plus literal. %20 selalu aman; + konvensi lama yang bertahan gara-gara perilaku form HTML.

Bagaimana cara URL-encode teks di Python, JavaScript, Go, dan Java?

JavaScript: encodeURIComponent('hello world') menghasilkan hello%20world. Python: urllib.parse.quote('hello world') menghasilkan hello%20world. Go: url.QueryEscape("hello world") menghasilkan hello+world. Java: URLEncoder.encode("hello world", UTF_8) menghasilkan hello+world. Go dan Java default ke form encoding (spasi jadi +) — ganti + dengan %20 kalau butuh output RFC 3986.

Bisakah URL encoding digunakan untuk keamanan atau enkripsi?

Tidak. URL encoding sepenuhnya reversible tanpa key — siapa saja bisa decode seketika. Nol kerahasiaan. Lindungi data sensitif pakai HTTPS (enkripsi TLS), bukan percent encoding. URL muncul di log server, riwayat browser, dan header Referer — data sensitif masukkan ke body request, bukan URL.

Apa itu double encoding dan bagaimana cara memperbaikinya?

Double encoding terjadi saat string yang sudah ter-encode kena encode lagi. % dalam %20 jadi %25, hasilnya %2520. Server lihat literal %20, bukan spasi. Fix-nya: decode input dulu, baru encode sekali. Pola %25 diikuti dua digit hex — tanda pasti double encoding.

Berapa panjang URL maksimum?

Spesifikasi HTTP tidak tetapkan batas resmi, tapi 2.000 karakter jadi patokan aman untuk kompatibilitas luas. Apache default 8.190 byte, Nginx 8.192 byte. Karakter non-ASCII mengembang 3-12x saat percent-encode, jadi URL internasional lebih cepat mentok batas. Untuk payload besar, pakai POST dengan body request.

Artikel Terkait

Lihat semua artikel