Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 35 of 54

Superglobals, headers and redirects

Read request details from $_SERVER, send status codes and headers, redirect after a form with the Post/Redirect/Get pattern, and return JSON from a PHP page.

13 minPHP on the webUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonInspect the request, set status codes and headers, redirect correctly, and return JSON responses.

Superglobals

PHP fills a set of arrays called superglobals before your script starts. Unlike ordinary variables, they are visible in every function without global:

Superglobal Holds
$_GET query string parameters
$_POST form fields sent with POST
$_COOKIE cookies sent by the browser (Cookies)
$_SESSION session data, after session_start() (Sessions)
$_FILES uploaded files (File uploads)
$_SERVER details about the request and the server
$_ENV environment variables (often empty; use getenv())

Reading the request from $_SERVER

$_SERVER describes the current request. The keys you will use most:

php
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "Method: " . $_SERVER['REQUEST_METHOD'] . "\n";
echo "URI: " . $_SERVER['REQUEST_URI'] . "\n";
echo "Path: " . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . "\n";
echo "Query: " . ($_SERVER['QUERY_STRING'] ?? '') . "\n";
echo "Accept: " . ($_SERVER['HTTP_ACCEPT'] ?? 'not sent') . "\n";
echo "Language: " . ($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'not sent') . "\n";
http
GET /request.php?sort=price&page=2
Accept: text/html
Accept-Language: en-IN
result
Method: GET
URI: /request.php?sort=price&page=2
Path: /request.php
Query: sort=price&page=2
Accept: text/html
Language: en-IN

Every request header appears as HTTP_ plus its name in capitals, with dashes turned into underscores. Treat all of these as user input: the browser, or anyone else, can send any value. That includes HTTP_HOST, HTTP_REFERER and HTTP_USER_AGENT. REMOTE_ADDR holds the visitor's IP address as seen by the server; behind a proxy or CDN it is the proxy's address.

Status codes and headers

An HTTP response has a status code, then headers, then the body. PHP sends 200 OK and a text/html content type unless you say otherwise. http_response_code() sets the status and header() adds or replaces a header:

php
<?php
$products = [1 => 'Mechanical Keyboard', 2 => 'Ceramic Mug Set'];
$id = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT);

if ($id === false || !isset($products[$id])) {
    http_response_code(404);
    echo "<h1>Product not found</h1>";
    exit;
}
echo "<h1>" . htmlspecialchars($products[$id]) . "</h1>";
http
GET /product.php?id=7
result
HTTP/1.1 404 Not Found

<h1>Product not found</h1>

Returning a real 404 for a missing page matters: search engines drop such URLs instead of indexing an error page as if it were content.

Headers already sent

Headers travel before the body. As soon as your script prints anything, even a space or a blank line before <?php, PHP sends the headers, and it is too late to add more:

php
<?php
echo "Hello";
header('Location: /elsewhere.php');
echo "\n";
result
Hello
Warning: Cannot modify header information - headers already sent by (output started at /path/to/late.php:2) in /path/to/late.php on line 3

The message tells you where the output started, which is where to look. The usual culprits are whitespace before <?php, a closing ?> followed by a blank line in an included file (which is why PHP-only files leave out the closing tag), or a debugging echo. Do all header work, redirects and cookies at the top of the script, before any HTML.

Redirects

A redirect tells the browser to load another URL. Send a Location header with a 3xx status, then stop the script with exit: header() only adds a header and does not end anything by itself.

php
<?php
header('Location: /new-page.php', true, 301);
exit;
http
GET /old-page.php
result
HTTP/1.1 301 Moved Permanently
Location: /new-page.php

Use 301 when a page has moved for good (search engines transfer its ranking), 302 or 303 for a temporary redirect, such as after a form.

Post/Redirect/Get

If a page that just processed a POST shows its result directly, refreshing the page makes the browser send the POST again: the order is placed twice, the message sent twice. The fix is the Post/Redirect/Get pattern: process the POST, then redirect to a normal GET page that shows the result.

php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $orderId = 1043; // save the order here and get its id
    header('Location: /thanks.php?order=' . $orderId, true, 303);
    exit;
}
echo '<form method="post"><button>Place order</button></form>';
php
<?php
$order = filter_var($_GET['order'] ?? '', FILTER_VALIDATE_INT);
echo "<p>Thank you! Your order number is " . ($order ?: 'unknown') . ".</p>";
http
POST /order.php
Content-Type: application/x-www-form-urlencoded

confirm=1
result
HTTP/1.1 303 See Other
Location: /thanks.php?order=1043

The browser then requests /thanks.php?order=1043, and refreshing that page is harmless. Status 303 See Other explicitly tells the browser to follow up with a GET.

Returning JSON

PHP pages can just as easily answer with JSON, for JavaScript on your own site or for other programs. Set the content type, then print json_encode():

php
<?php
header('Content-Type: application/json');
$product = ['id' => 1, 'name' => 'Mechanical Keyboard', 'price' => 89.5, 'inStock' => true];
echo json_encode($product);
http
GET /api-product.php
result
HTTP/1.1 200 OK
Content-Type: application/json

{"id":1,"name":"Mechanical Keyboard","price":89.5,"inStock":true}

JSON is covered fully in JSON: encoding and decoding data.

Common mistakes

  • Output before header(), including whitespace before <?php or after ?> in an included file.
  • Forgetting exit after a Location header, so the rest of the script still runs (and may do something it should not).
  • Showing the result of a POST directly instead of redirecting.
  • Trusting $_SERVER values that come from request headers, such as the referrer.

Exercise

Write go.php that redirects ?to=learn to /learn/ and ?to=tools to /tools/ with a 302, and answers anything else with a 404 and the text "Unknown destination". Never put the raw to value into the Location header.

Show one possible answer
php
<?php
$targets = ['learn' => '/learn/', 'tools' => '/tools/'];
$to = $_GET['to'] ?? '';

if (!isset($targets[$to])) {
    http_response_code(404);
    echo "Unknown destination";
    exit;
}
header('Location: ' . $targets[$to], true, 302);
exit;
http
GET /go.php?to=tools
result
HTTP/1.1 302 Found
Location: /tools/

Redirecting only to URLs from your own list avoids an open redirect, where attackers use your domain to send people to a phishing site.

Key points

  • Superglobals such as $_SERVER, $_GET, $_POST, $_COOKIE, $_SESSION and $_FILES are available everywhere.
  • $_SERVER['REQUEST_METHOD'] and $_SERVER['REQUEST_URI'] describe the request.
  • header() and http_response_code() must be called before any output, or PHP reports 'headers already sent'.
  • After a successful POST, redirect with a 303 to a GET page (Post/Redirect/Get) and call exit.

Check yourself

1. Why must header() be called before any output?
2. What should follow header('Location: /thanks.php')?
3. What problem does Post/Redirect/Get solve?
4. Which status code means 'not found'?