Imagine a visitor submits a contact form, sees “Thank you!” and then refreshes the page. The browser asks: “Resend the form data?” If they say yes, the message is sent twice.
The cure is the Post/Redirect/Get pattern (PRG):
- The form is sent with POST.
- PHP processes it, then redirects the browser to a normal page.
- The browser loads that page with GET.
A refresh now repeats only the harmless GET.
The redirect
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// validate and save ...
header("Location: thanks.php");
exit;
}
Two rules:
header()must run before any output, including spaces and HTML. If it does not, PHP warns “headers already sent”.- Always call
exit;right after, so the rest of the script does not keep running.
Show a message after the redirect: flash messages
A redirect starts a brand-new request, so a variable from the first request is gone. Store the message in the session for one request.
<?php
session_start();
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// validate and save ...
$_SESSION["flash"] = "Thank you! Your message was sent.";
header("Location: contact.php");
exit;
}
$flash = $_SESSION["flash"] ?? null;
unset($_SESSION["flash"]); // show it only once
?>
<?php if ($flash): ?>
<p role="status"><?= htmlspecialchars($flash) ?></p>
<?php endif; ?>
unset() removes the message after it is shown, so a second refresh shows nothing. Sessions are explained in Chapter 16.
Only redirect on success
If validation fails, do not redirect. Show the form again right away with the errors and the old values (see Preserving Form Values). A redirect would lose them.
| Result | What to do |
|---|---|
| Validation failed | Show the form again with errors, status 200 |
| Success | Redirect (303 or 302) to a GET page |
Which status code?
header("Location: ...") sends status 302 by default, and browsers switch to GET. If you want to be explicit, send 303 See Other:
<?php
header("Location: thanks.php", true, 303);
exit;
Redirect safely
Never redirect to a URL taken straight from user input (?next=...). That is an open redirect, which attackers use for phishing. Check that the target is one of your own pages.
<?php
function safeRedirectTarget(string $target): string
{
$allowed = ["/dashboard.php", "/profile.php"];
return in_array($target, $allowed, true) ? $target : "/dashboard.php";
}
echo safeRedirectTarget("https://evil.example") . "\n";
echo safeRedirectTarget("/profile.php");
/dashboard.php
/profile.php
Output before header()
<?php, a echo or a byte-order mark in the file counts as output. If you see “Cannot modify header information”, look for stray output above the header() call.