$_POST
What Is $_POST?
In simple terms: $_POST is an array containing the data a browser sends when a user submits a form with method="post". Each form field's name becomes a key, and what the user entered becomes the value. Technically: $_POST is a superglobal that PHP populates from the body of an HTTP request when the request's Content-Type is application/x-www-form-urlencoded (the default for HTML forms) or multipart/form-data (forms with file uploads). Other body formats, such as JSON, do not populate $_POST.
Why POST Is Used for Forms
| GET | POST | |
|---|---|---|
| Where data travels | In the URL | In the request body |
| Visible in address bar, history, server logs | Yes | No |
| Can be bookmarked or shared as a link | Yes | No |
| Typical size limit | A few kilobytes (URL length) | Configurable, megabytes (post_max_size) |
| Meaning | "Fetch something" (safe, repeatable) | "Do something" (create, update, delete, log in) |
Use POST for anything that changes data or contains sensitive information (passwords, personal details), and for large inputs.
A Basic Form
subscribe.php
<?php
$message = '';
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$email = trim($_POST['email'] ?? '');
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$message = 'Please enter a valid email address.';
} else {
// In a real application: save to the database here
$message = 'Subscribed: ' . htmlspecialchars($email);
}
}
?>
<form method="post" action="">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
<p><?= $message ?></p>
What Happens
- First visit (GET): the request method is
GET, so the form is shown with an empty message. - Submit: the browser sends a POST request whose body contains
email=asha%40example.com. - PHP decodes the body and fills
$_POST:['email' => 'asha@example.com']. - The script validates the email and shows a message. The email is escaped with
htmlspecialchars()before being printed.
Output After Submitting asha@example.com
<p>Subscribed: asha@example.com</p>
Key Points
- The array key comes from the field's
nameattribute — not fromid. A field withoutnameis not sent. action=""submits the form to the current URL.$_SERVER['REQUEST_METHOD'] === 'POST'distinguishes "show the form" from "process the form".?? ''provides a default, because a manipulated request might not include the field.
Testing POST Requests With curl
You do not need a browser to send POST data. With the built-in server running (php -S localhost:8000):
curl -X POST -d "email=asha@example.com" http://localhost:8000/subscribe.php
-d sends form-encoded data in the body. This is also a reminder that anyone can send any POST data to your script, without using your form.
Form Field Types and $_POST
| Field | What Is Sent |
|---|---|
| Text, email, password, textarea | The entered text (possibly an empty string) |
| Select | The value of the chosen option |
Radio buttons (same name) | The value of the selected button; nothing if none selected |
| Checkbox | Its value (default "on") if checked; nothing if unchecked |
| Disabled fields | Nothing |
Submit button with a name | Its value, if that button was clicked |
Because unchecked checkboxes and unselected radio buttons are not sent at all, always read them with a default:
$newsletter = isset($_POST['newsletter']); // checkbox: true if checked
$plan = $_POST['plan'] ?? 'free'; // radio: default if none chosen
Arrays From Form Fields
Field names with square brackets create arrays in $_POST:
<input type="checkbox" name="skills[]" value="php">
<input type="checkbox" name="skills[]" value="mysql">
<input type="text" name="address[city]">
<input type="text" name="address[pin]">
PHP parses these names exactly like parse_str() parses a query string, which lets you see the result without a browser:
<?php
$body = 'name=Asha&skills%5B%5D=php&skills%5B%5D=mysql&address%5Bcity%5D=Chennai&address%5Bpin%5D=600042';
parse_str($body, $data);
print_r($data);
Output
Array
(
[name] => Asha
[skills] => Array
(
[0] => php
[1] => mysql
)
[address] => Array
(
[city] => Chennai
[pin] => 600042
)
)
skills[](empty brackets) collects multiple values into a list — ideal for checkbox groups and multi-selects.address[city]creates a nested associative array, which is useful for grouping related fields.- In the body,
[and]are URL-encoded as%5Band%5D.
Every Field Can Be String or Array
Because the client chooses field names, a field you expect to be a string can arrive as an array (name[]=x), and vice versa. Passing an unexpected array to a string function throws a TypeError. A small helper prevents this:
<?php
function postString(string $key, string $default = ''): string
{
$value = $_POST[$key] ?? $default;
return is_string($value) ? trim($value) : $default;
}
function postArray(string $key): array
{
$value = $_POST[$key] ?? [];
return is_array($value) ? $value : [];
}
Practical Example: Contact Form With Validation and Redirect
contact.php
<?php
session_start();
function postString(string $key): string
{
$value = $_POST[$key] ?? '';
return is_string($value) ? trim($value) : '';
}
$errors = [];
$old = ['name' => '', 'email' => '', 'message' => ''];
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$old = [
'name' => postString('name'),
'email' => postString('email'),
'message' => postString('message'),
];
if ($old['name'] === '') {
$errors['name'] = 'Please enter your name.';
}
if (filter_var($old['email'], FILTER_VALIDATE_EMAIL) === false) {
$errors['email'] = 'Please enter a valid email address.';
}
if (mb_strlen($old['message']) < 10) {
$errors['message'] = 'Message must be at least 10 characters.';
}
if ($errors === []) {
// Save the message or send an email here.
$_SESSION['flash'] = 'Thank you, ' . $old['name'] . '! We will reply soon.';
header('Location: /contact.php', true, 303);
exit;
}
}
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
?>
<?php if ($flash !== null): ?>
<p class="success"><?= e($flash) ?></p>
<?php endif; ?>
<form method="post" action="/contact.php" novalidate>
<label>Name <input name="name" value="<?= e($old['name']) ?>"></label>
<?php if (isset($errors['name'])): ?><span class="error"><?= e($errors['name']) ?></span><?php endif; ?>
<label>Email <input type="email" name="email" value="<?= e($old['email']) ?>"></label>
<?php if (isset($errors['email'])): ?><span class="error"><?= e($errors['email']) ?></span><?php endif; ?>
<label>Message <textarea name="message"><?= e($old['message']) ?></textarea></label>
<?php if (isset($errors['message'])): ?><span class="error"><?= e($errors['message']) ?></span><?php endif; ?>
<button type="submit">Send</button>
</form>
How It Behaves
| Scenario | Result |
|---|---|
| First visit | Empty form |
| Submitted with errors (e.g. invalid email) | Form shown again with the user's values filled in and an error next to each invalid field |
| Submitted successfully | Browser is redirected to /contact.php, which shows "Thank you, Asha! We will reply soon." |
| Page refreshed after success | The message disappears; nothing is submitted again |
Step-by-Step Explanation
- Reading input:
postString()trims values and ignores unexpected arrays. - Validation: each rule adds an error keyed by field name, so the template can show it next to the right field.
- Keeping user input:
$oldholds what the user typed, so the form can be refilled after an error — users should not have to retype everything. - Post/Redirect/Get (PRG): after a successful submission, the script redirects with status 303 See Other, which tells the browser to load the page with a GET request. Refreshing the page then repeats the harmless GET, not the POST — preventing duplicate messages or orders.
- Flash message: the success message is stored in the session before the redirect and removed after it is displayed once (see PHP Sessions).
- Escaping: every value printed into HTML goes through
e(), including the user's own input. novalidateturns off the browser's built-in validation so you can see the server-side checks working. Browser validation improves user experience, but server-side validation is always required, because requests can bypass the browser. A production form would also include a CSRF token (see CSRF Protection).
JSON Request Bodies
JavaScript applications and APIs often send JSON instead of form data:
curl -X POST -H "Content-Type: application/json" \
-d '{"name": "Asha", "plan": "pro"}' \
http://localhost:8000/api/subscribe.php
For such requests, $_POST is empty. Read the raw body instead:
<?php
// api/subscribe.php
header('Content-Type: application/json');
try {
$data = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
$name = is_array($data) && is_string($data['name'] ?? null) ? trim($data['name']) : '';
if ($name === '') {
http_response_code(422);
echo json_encode(['error' => 'Name is required']);
exit;
}
echo json_encode(['message' => "Welcome, $name"]);
Response for the curl Request Above
{"message":"Welcome, Asha"}
php://inputis a read-only stream containing the raw request body.JSON_THROW_ON_ERRORmakes invalid JSON throw aJsonException, which is answered with 400 Bad Request.- Missing or invalid fields are answered with 422 Unprocessable Content, a common status for validation errors in APIs.
Size Limits
php.ini Setting | Default | Effect |
|---|---|---|
post_max_size | 8M | Maximum size of the entire POST body |
max_input_vars | 1000 | Maximum number of input fields; extra fields are dropped with a warning |
upload_max_filesize | 2M | Maximum size per uploaded file (see PHP File Upload) |
Important: When a request body exceeds
post_max_size, PHP discards it:$_POSTand$_FILESare empty, and a warning is logged. A form that "submits but loses all data" is often caused by this. Check$_SERVER['CONTENT_LENGTH']against the limit to show a helpful error.
Common Mistakes
Missing
nameAttributesEffect: Fields are not sent;
$_POSTlacks the expected keys. Fix: Give every input aname.Reading Fields Without Defaults
Effect: Warnings for unchecked checkboxes and manipulated requests. Fix: Use
?? '',isset(), or helper functions.Trusting Hidden Fields and Select Values
Effect: Users change prices, IDs, or roles in the browser's developer tools. Fix: Validate against allowed values and look up important data (prices, permissions) on the server.
Printing Input Unescaped
Effect: Cross-site scripting — even when refilling the user's own form. Fix: Escape with
htmlspecialchars().Processing the Form and Showing a Page in the Same Response
Effect: Refreshing the page submits the form again. Fix: Redirect after a successful POST (Post/Redirect/Get).
Expecting JSON in
$_POSTEffect:
$_POSTis empty for JSON requests. Fix: Readphp://inputandjson_decode()it.
Best Practices
- Use POST for actions that change data and for sensitive or large inputs.
- Check the request method explicitly, then validate every field on the server.
- Read fields with defaults and type checks; never assume a field is a string.
- Refill forms with escaped user input after validation errors.
- Redirect after successful submissions (PRG), and protect forms with CSRF tokens. Official reference: https://www.php.net/manual/en/reserved.variables.post.php