Article · Apr 24, 2026 · 6 min read

URL encoding in PHP

PHP has had URL encoding functions since forever, but it also has two near-identical functions with a single, important difference. Pick the wrong one and your URLs work in some places and break in others. This article covers what each function does, when to use it, and how to build a clean query string in modern PHP.

The four functions you need

urlencode($string) — Form-encoded variant. Space becomes +. The default for HTML form submissions and query strings.

rawurlencode($string) — RFC 3986 percent-encoding. Space becomes %20. The strict, modern standard.

urldecode($string) — Reverses urlencode. Both %XX and + are decoded (the latter becomes space).

rawurldecode($string) — Reverses rawurlencode. Only %XX is decoded; + stays literal.

The one-line decision

For new code, use rawurlencode and rawurldecode. They’re RFC 3986 compliant and produce predictable output.

Use urlencode only when you specifically need form-encoded output that matches HTML form submission behavior (typically when building application/x-www-form-urlencoded POST bodies manually).

urlencode vs rawurlencode side-by-side

<?php
$input = 'Hello, World!';

echo urlencode($input);
// Hello%2C+World%21          ← space is +

echo rawurlencode($input);
// Hello%2C%20World%21        ← space is %20

That’s literally the only difference. Comma, exclamation, and every other special character encode identically.

Building a query string with http_build_query

For query strings, don’t concatenate manually. Use http_build_query:

<?php
$params = [
    'q' => 'Hello, World!',
    'lang' => 'en',
    'page' => 2,
];

echo http_build_query($params);
// q=Hello%2C+World%21&lang=en&page=2

By default, http_build_query uses urlencode-style encoding (space → +). Pass the PHP_QUERY_RFC3986 flag to get %20 instead:

echo http_build_query($params, '', '&', PHP_QUERY_RFC3986);
// q=Hello%2C%20World%21&lang=en&page=2

Nested arrays and PHP’s bracket syntax

PHP has its own convention for arrays in query strings — bracket notation:

<?php
$params = [
    'user' => [
        'name' => 'Alice',
        'age' => 30,
    ],
    'tags' => ['php', 'url', 'encoding'],
];

echo http_build_query($params);
// user%5Bname%5D=Alice&user%5Bage%5D=30&tags%5B0%5D=php&tags%5B1%5D=url&tags%5B2%5D=encoding
// (decodes to user[name]=Alice&user[age]=30&tags[0]=php&...)

This is great when the receiver is also PHP — $_GET rebuilds the nested structure automatically. Less great when sending to non-PHP services, which often don’t understand the bracket notation.

Decoding URLs

The parsing side is symmetric:

<?php
echo urldecode('Hello%2C+World%21');
// Hello, World!         ← + decoded as space

echo rawurldecode('Hello%2C+World%21');
// Hello, World!         ← but wait, + stayed as + here
// Actually: 'Hello, +World!'

And to parse a query string:

<?php
parse_str('q=hello&lang=en', $params);
print_r($params);
// Array ( [q] => hello [lang] => en )

// Also handles nested:
parse_str('user[name]=Alice&user[age]=30', $params);
// $params['user']['name'] === 'Alice'

Be careful with parse_str — it injects variables into the current scope by default. Always pass the second argument so the result goes into an array instead.

Real-world examples

Building a search URL

<?php
$base = 'https://api.example.com/search';
$query = http_build_query([
    'q' => $userQuery,
    'lang' => 'en',
    'page' => $page,
]);
$url = "$base?$query";

Encoding a path segment

<?php
$productName = 'My Item / Foo';
$path = '/products/' . rawurlencode($productName);
// '/products/My%20Item%20%2F%20Foo'

Modifying an existing URL

<?php
$url = 'https://example.com/page?ref=ad';
$parsed = parse_url($url);
parse_str($parsed['query'] ?? '', $params);
$params['utm_source'] = 'twitter';
$parsed['query'] = http_build_query($params);
$newUrl = "{$parsed['scheme']}://{$parsed['host']}{$parsed['path']}?{$parsed['query']}";
// 'https://example.com/page?ref=ad&utm_source=twitter'

Common PHP URL encoding mistakes

Mistake 1: Using urlencode for path segments

<?php
// Wrong — the + means literal + in a path, not space
$url = '/users/' . urlencode('John Doe');
// '/users/John+Doe'   ← the + is now part of the name

// Right
$url = '/users/' . rawurlencode('John Doe');
// '/users/John%20Doe'

Mistake 2: Double-encoding from $_GET

PHP automatically decodes $_GET values. Encoding them again is wrong:

<?php
// User visits ?q=Hello%2C+World%21
// $_GET['q'] is already 'Hello, World!' (PHP decoded it)

// Wrong:
$url = "?q=" . urlencode($_GET['q']);   // first decode, then re-encode is fine
$url = "?q=" . $_GET['q'];              // but using raw is broken — special chars unencoded

// Right: encode once when building the URL
$url = "?q=" . urlencode($_GET['q']);

Mistake 3: Confusion about which function to use

If you’re unsure, ask: "will this output be a value inside a query string (use either) or a value inside a URL path (use rawurlencode only)?"

Quick reference card

Goal Function
Modern RFC 3986 encodingrawurlencode()
Form-encoded (legacy)urlencode()
Build query string from arrayhttp_build_query()
Decode RFC 3986rawurldecode()
Decode form-encodedurldecode()
Parse a URL into partsparse_url()
Parse query into arrayparse_str()

Default to rawurlencode for new code, http_build_query for building query strings, and reach for the legacy urlencode only when you specifically need form-encoded output to match an HTML form’s wire format.


Found this useful? Try the URL decoder, the URL encoder, or browse all tools.

More reading

From the blog.