Skip to content

Server-Side Input Validation in PHP

User input is the main entry point for attacks on your application. Validation ensures data is safe and meets your requirements before processing.


Follow these steps for every input field:

Use filter_input() to safely retrieve values.

// For form data (POST)
$email = filter_input(INPUT_POST, 'email');
// For query string parameters (GET)
$id = filter_input(INPUT_GET, 'id');
// With validation (retrieve + validate in one step)
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);

Remove leading/trailing spaces from string inputs.

$email = trim($email ?? '');
$search = trim($search ?? '');

If the field is required, check that it’s not empty.

if (empty($email)) {
$errors[] = 'Email is required';
}

Check that the data matches the expected format. Common examples:

  • Email addresses: must contain @ and valid domain
  • Phone numbers: digits only, specific length (e.g., 10 digits)
  • Postal codes: format varies by country (e.g., A1A 1A1 in Canada, 12345 in USA)
  • ISBN: 10 or 13 digits with optional hyphens
  • URLs: must start with http:// or https://
  • Dates: specific format like YYYY-MM-DD
  • Credit cards: 16 digits, passes Luhn algorithm
  • Usernames: letters and numbers only, no spaces
// Email format
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email format';
}
// Integer
if (!filter_var($id, FILTER_VALIDATE_INT)) {
$errors[] = 'ID must be a number';
}

Check application-specific constraints. Common examples:

  • Minimum/maximum length: username must be 3-20 characters
  • Number ranges: age must be 18-120, quantity must be 1-99
  • Allowed values: status must be “active”, “pending”, or “inactive”
  • Uniqueness: email or username must not already exist in database
  • Relationships: end date must be after start date
  • Permissions: user must own the resource they’re editing
  • Stock limits: order quantity cannot exceed available inventory
// Length check
if (strlen($username) < 3 || strlen($username) > 20) {
$errors[] = 'Username must be 3-20 characters';
}
// Range check
if ($age < 18 || $age > 120) {
$errors[] = 'Age must be between 18 and 120';
}
// Allowed values
$allowed = ['red', 'blue', 'green'];
if (!in_array($color, $allowed)) {
$errors[] = 'Invalid color selection';
}

if (empty($errors)) {
// Data is valid - process it
} else {
// Show errors to user
}

Apply the procedure to a registration form:

$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Step 1 & 2: Retrieve and trim
$username = trim(filter_input(INPUT_POST, 'username') ?? '');
$email = trim(filter_input(INPUT_POST, 'email') ?? '');
$password = filter_input(INPUT_POST, 'password') ?? '';
// Step 3: Required fields
if (empty($username)) {
$errors[] = 'Username is required';
}
if (empty($email)) {
$errors[] = 'Email is required';
}
if (empty($password)) {
$errors[] = 'Password is required';
}
// Step 4: Format validation
if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email format';
}
// Step 5: Business rules
if (!empty($username) && strlen($username) < 3) {
$errors[] = 'Username must be at least 3 characters';
}
if (!empty($password) && strlen($password) < 8) {
$errors[] = 'Password must be at least 8 characters';
}
// Step 6: Process or show errors
if (empty($errors)) {
// Save to database...
}
}

Apply the procedure to URL parameters like products.php?id=5&page=2&search=laptop:

$errors = [];
// Step 1: Retrieve with type validation
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT);
$search = trim(filter_input(INPUT_GET, 'search') ?? '');
// Step 3 & 4: Validate required parameters
if ($id === false || $id === null) {
$errors[] = 'Invalid product ID';
}
// Step 5: Business rules
if ($page !== null && $page < 1) {
$page = 1; // Default to page 1
}
if (strlen($search) > 100) {
$errors[] = 'Search query too long';
}
// Step 6: Process or show errors
if (empty($errors)) {
$product = fetchProductById($id);
if (!$product) {
$errors[] = 'Product not found';
}
}

$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (!$id || $id < 1) {
die('Invalid ID');
}
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?? 1;
$page = max(1, $page); // Ensure minimum of 1
$search = trim(filter_input(INPUT_GET, 'q') ?? '');
$search = substr($search, 0, 100); // Limit length
$sort = filter_input(INPUT_GET, 'sort') ?? 'newest';
$allowed_sorts = ['newest', 'oldest', 'price_asc', 'price_desc'];
if (!in_array($sort, $allowed_sorts)) {
$sort = 'newest'; // Default to safe value
}

FunctionPurposeExample
filter_input(INPUT_POST, 'name')Safely get POST valueReturns value or null
filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT)Get and validate as integerReturns int, false, or null
filter_var($email, FILTER_VALIDATE_EMAIL)Validate email formatReturns email or false
empty($value)Check if empty/null/falseReturns true or false
trim($string)Remove whitespace" hello ""hello"
strlen($string)Get string length"hello"5
is_numeric($value)Check if numeric"123"true
in_array($value, $array)Check if in allowed listReturns true or false
ctype_alnum($string)Check letters/numbers only"abc123"true

  1. Skipping server-side validation - Client-side validation can be bypassed
  2. Using $_GET/$_POST directly - Use filter_input() for safer retrieval
  3. Not validating IDs - Always validate before database queries
  4. Vague error messages - Tell users what’s wrong so they can fix it
  5. Not preserving valid input - Keep valid fields filled when showing errors
  6. Forgetting edge cases - Test empty, very long, and special character inputs