$_GET
What Is $_GET?
In simple terms: $_GET is an array containing the parameters in the URL after the ? — the query string. For the address products.php?category=books&page=2, $_GET contains ['category' => 'books', 'page' => '2']. Technically: $_GET is a superglobal that PHP fills by parsing the query string of the requested URL, regardless of the request method. Parameters arrive from links, from forms submitted with method="get", and from any URL typed or generated by a client.
When GET Parameters Are the Right Choice
GET parameters describe what to show, not what to change:
| Good Uses | Avoid For |
|---|---|
| Search terms | Passwords, tokens, personal data |
| Filters and sorting | Creating, updating, or deleting data |
| Pagination | Large amounts of data |
Selecting an item to view (?id=42) | Anything that must not be repeated by reloading |
Tracking the source of a visit (?ref=newsletter) |
Because the parameters are part of the URL, the resulting page can be bookmarked, shared, and reloaded — which is exactly what users expect from search results and product listings. The same property makes GET unsuitable for sensitive data: URLs are stored in browser history, server logs, analytics tools, and Referer headers.
Reading Query Parameters
<?php
// Request: GET /products.php?category=books&page=2&sort=price_asc
$category = $_GET['category'] ?? 'all';
$page = $_GET['page'] ?? '1';
$sort = $_GET['sort'] ?? 'newest';
$search = $_GET['q'] ?? '';
var_dump($category, $page, $sort, $search);
Output
string(5) "books"
string(1) "2"
string(9) "price_asc"
string(0) ""
Key Points
- Every value is a string — even
page, which looks like a number. Validate and convert before using it as an integer. - Parameters that are not in the URL (
q) are missing from$_GET;??provides a default without a warning. - Parameter names are case-sensitive:
?Page=2does not set$_GET['page'].
How the Query String Is Parsed
PHP decodes the query string with the same rules as parse_str(), which lets you see the result directly:
<?php
$queryString = 'q=php+%26+mysql&tags[]=new&tags[]=sale&price[min]=100&price[max]=500&empty=&flag';
parse_str($queryString, $params);
var_dump($params);
Output
array(6) {
["q"]=>
string(11) "php & mysql"
["tags"]=>
array(2) {
[0]=>
string(3) "new"
[1]=>
string(4) "sale"
}
["price"]=>
array(2) {
["min"]=>
string(3) "100"
["max"]=>
string(3) "500"
}
["empty"]=>
string(0) ""
["flag"]=>
string(0) ""
}
What This Shows
| In the URL | In $_GET |
|---|---|
+ | A space |
%26 | & (URL-encoded characters are decoded) |
tags[]=new&tags[]=sale | A list: ['new', 'sale'] |
price[min]=100 | A nested array: ['min' => '100'] |
empty= and flag (no =) | Empty strings |
Validating GET Parameters
Since users can type any URL, every parameter needs validation.
Integers
<?php
// Request: GET /products.php?page=abc
$page = filter_var($_GET['page'] ?? 1, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if ($page === false) {
$page = 1;
}
var_dump($page);
Output
int(1)
FILTER_VALIDATE_INT returns an int for valid input and false otherwise. min_range rejects zero and negative pages.
Allow-Lists for Fixed Choices
<?php
// Request: GET /products.php?sort=price;DROP
$allowedSorts = ['newest', 'price_asc', 'price_desc', 'rating'];
$sort = $_GET['sort'] ?? 'newest';
if (!in_array($sort, $allowedSorts, true)) {
$sort = 'newest';
}
echo $sort . "\n";
Output
newest
Values such as sort orders often end up in SQL (ORDER BY), where placeholders cannot be used for column names. An allow-list is the safe way to handle them: only known values are ever used.
filter_input()
filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) reads and validates in one step. It returns null if the parameter is missing and false if it is invalid. Note that it reads the original request data (changes to $_GET do not affect it) and returns null in command-line scripts, which can make testing harder. Validation is covered in detail in PHP Filters.
The "String or Array" Problem
The client controls the shape of every parameter. Adding [] to a name turns a string into an array:
/search.php?q=php → $_GET['q'] is "php"
/search.php?q[]=php → $_GET['q'] is ["php"]
Code that passes the value to a string function then fails:
<?php
// Request: GET /search.php?q[]=php
try {
$term = trim($_GET['q'] ?? '');
} catch (TypeError $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
Output
TypeError: trim(): Argument #1 ($string) must be of type string, array given
An uncaught TypeError produces a server error (500) — easy for anyone to trigger. Check the type when reading parameters:
<?php
function queryString(string $key, string $default = ''): string
{
$value = $_GET[$key] ?? $default;
return is_string($value) ? trim($value) : $default;
}
Escaping GET Values in Output
Displaying a search term back to the user is a classic cross-site scripting (XSS) vulnerability:
<!-- Vulnerable -->
<p>Results for: <?= $_GET['q'] ?></p>
A link such as /search.php?q=<script>stealCookies()</script> would execute the script in the victim's browser. Always escape:
<p>Results for: <?= htmlspecialchars($_GET['q'] ?? '') ?></p>
The same applies when inserting values into HTML attributes, JavaScript, or URLs. See Escaping Output (htmlspecialchars) and XSS Prevention.
Building URLs With Query Parameters
Never build query strings by concatenating raw values — special characters such as &, =, #, and spaces break the URL. Use http_build_query():
<?php
$params = ['q' => 'php & mysql', 'category' => 'books', 'page' => 2];
echo '/search.php?' . http_build_query($params) . "\n";
echo '/search.php?q=' . urlencode('C# basics') . "\n";
Output
/search.php?q=php+%26+mysql&category=books&page=2
/search.php?q=C%23+basics
http_build_query()encodes keys and values and joins them with&.urlencode()encodes a single value; without it,#would start a URL fragment and the rest of the search term would be lost.- When printing a URL into an HTML attribute, also escape it with
htmlspecialchars(), because&must be written as&in HTML.
Practical Example: Product Listing With Filters and Pagination
<?php
function readFilters(array $query): array
{
$allowedSorts = ['newest', 'price_asc', 'price_desc'];
$sort = is_string($query['sort'] ?? null) && in_array($query['sort'], $allowedSorts, true)
? $query['sort']
: 'newest';
$category = is_string($query['category'] ?? null) && preg_match('/^[a-z-]{1,30}$/', $query['category'])
? $query['category']
: null;
$page = filter_var($query['page'] ?? 1, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
return ['category' => $category, 'sort' => $sort, 'page' => $page === false ? 1 : $page];
}
function listingUrl(array $filters, array $changes = []): string
{
$params = array_filter(
array_replace($filters, $changes),
fn($value) => $value !== null && $value !== '' && $value !== 1
);
return '/products.php' . ($params === [] ? '' : '?' . http_build_query($params));
}
// Simulates: GET /products.php?category=books&sort=price_desc&page=2&utm=promo
$filters = readFilters(['category' => 'books', 'sort' => 'price_desc', 'page' => '2', 'utm' => 'promo']);
print_r($filters);
echo listingUrl($filters, ['page' => 3]) . "\n";
echo listingUrl($filters, ['sort' => 'newest', 'page' => 1]) . "\n";
echo listingUrl($filters, ['category' => null, 'page' => 1]) . "\n";
// In the real page: $filters = readFilters($_GET);
Output
Array
(
[category] => books
[sort] => price_desc
[page] => 2
)
/products.php?category=books&sort=price_desc&page=3
/products.php?category=books&sort=newest
/products.php?sort=price_desc
Step-by-Step Explanation
readFilters()receives the query array as a parameter ($_GETin real use), which makes it testable with any input.sortmust be in the allow-list.categorymust be a short lowercase slug; anything else means "no category filter" (null).pagemust be a positive integer.- Unknown parameters (such as
utm) are ignored.
listingUrl()builds links that keep the current filters while changing some of them:array_replace()applies the changes.array_filter()removes empty values andpage=1, so URLs stay short and canonical.http_build_query()encodes everything safely.
- The three links show typical uses: "next page", "change sorting and go back to page 1", and "remove the category filter". Changing a filter resets the page to 1, because page 3 of the old results may not exist in the new results. Keeping URLs canonical (no redundant parameters) also helps search engines treat equivalent pages as one.
Common Mistakes
Using GET for Actions That Change Data
Effect: Links, prefetching browsers, and search engine crawlers can trigger deletions or orders; CSRF attacks become trivial. Fix: Use POST for changes.
Putting Sensitive Data in URLs
Effect: Passwords or tokens leak through history, logs, and Referer headers. Fix: Send sensitive data in POST bodies or headers.
Treating Parameters as Numbers Without Validation
Effect: "2abc", "-5", or "99999999999" cause wrong results or errors. Fix: Validate with filter_var() and range options.
Assuming Parameters Are Strings
Effect: ?q[]=x triggers a TypeError and a 500 error. Fix: Check with is_string() before using string functions.
Printing Parameters Unescaped
Effect: Reflected XSS. Fix: htmlspecialchars() for HTML output.
Concatenating Raw Values Into URLs
Effect: Broken links and injected parameters. Fix: http_build_query() or rawurlencode()/urlencode().
Best Practices
- Use GET for safe, repeatable requests: viewing, searching, filtering, and paginating.
- Read parameters with defaults, check their types, and validate them against allow-lists or patterns.
- Escape parameters whenever they are printed.
- Build links with
http_build_query(), preserving current filters where appropriate. - Pass the query array into functions instead of reading
$_GETdeep inside your code. Official reference: https://www.php.net/manual/en/reserved.variables.get.php