Hidden Form Fields
The Concept
Hidden form fields embed state directly in HTML forms as invisible <input> elements. When the form is submitted, this hidden data travels with the visible form data.
<form action="/checkout" method="POST">
<input type="hidden" name="cart_id" value="abc123">
<input type="hidden" name="step" value="2">
<!-- Visible fields -->
<input type="text" name="address">
<button type="submit">Continue</button>
</form>
How It Works
The key insight: the server must actively embed the data into each response. The hidden fields don't persist on their own - PHP (or any server language) reads the incoming data and writes it back into the next form.
Multi-Step Form Demo
The classic use case for hidden fields is a multi-step wizard. Each step carries forward data from previous steps.
Start Multi-Step DemoComparison: Hidden Fields vs Other Methods
| Aspect | Hidden Fields | URL Parameters | Sessions |
|---|---|---|---|
| Visibility | In page source | In address bar | Server only |
| Persistence | Form submission only | Bookmarkable | Configurable |
| Size Limit | Unlimited | ~2KB | Unlimited |
| Security | User can modify | Visible to all | Most secure |
| Back Button | May lose data | Works perfectly | Works |
Critical Security Warning
Despite being invisible in the browser, users can:
- View them with "View Page Source"
- Modify them with browser DevTools
- Send arbitrary values using curl or scripts
<?php // DANGER: Trusting hidden field for authorization $price = $_POST['price']; // User changed this from 99.99 to 0.01! chargeCustomer($price); // VULNERABLE // SAFE: Server looks up the real price $productId = $_POST['product_id']; $price = getProductPrice($productId); // From database chargeCustomer($price); ?>
When to Use Hidden Fields
- Multi-step forms (wizard pattern)
- CSRF tokens (security)
- Form context that doesn't need to persist
- Carrying non-sensitive identifiers
- Sensitive data (passwords, tokens)
- Authorization decisions (prices, permissions)
- Data that should persist across page refreshes
The PHP Code
<?php
// step1.php - Collect name
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
?>
<form action="step2.php" method="POST">
<!-- Carry name forward as hidden field -->
<input type="hidden" name="name"
value="<?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>">
<label>Email: <input type="email" name="email" required></label>
<button type="submit">Next</button>
</form>
<?php
}
?>
Historical Note: ASP.NET ViewState
Microsoft's ASP.NET Web Forms (2002-2010s) made extensive use of hidden fields through a mechanism called ViewState. This automated the pattern we're doing manually above.
ASP.NET automatically serialized the entire page state (control values, properties, etc.) into a single hidden field:
<input type="hidden" name="__VIEWSTATE"
value="dDwtMTI4NzYzND...very long base64 string..." />
On each "postback" (form submission back to the same page), the framework would:
- Deserialize the ViewState
- Restore all control values
- Run event handlers
- Re-serialize the updated state
- Send back the new page with updated ViewState
This created the illusion of stateful, desktop-like programming on the stateless web. A button click felt like a Windows Forms event.
- Page bloat: ViewState could grow to 100KB+ of hidden data per page
- Performance: Serialization/deserialization overhead on every request
- Security: Early versions were not encrypted (data exposure)
- Bandwidth: All that data sent with every POST request
Modern frameworks (ASP.NET MVC, Core) abandoned this pattern in favor of explicit state management similar to what other frameworks use.
Understanding ViewState helps explain why hidden fields are powerful but should be used judiciously. The lesson: don't try to hide the stateless nature of HTTP - work with it.
Back to State Management | Sessions Demo | Security Patterns