Question
How to Sanitize User Input in PHP for SQL Injection and XSS
Question
I want to sanitize user input in PHP in a way that protects against both SQL injection and XSS attacks. Is there a single catch-all function that can handle both problems effectively while still allowing certain HTML tags in the submitted content?
Short Answer
By the end of this page, you will understand why there is no single PHP function that safely handles all kinds of user input. You will learn the difference between validation, sanitization, SQL injection prevention, and XSS prevention, and how to correctly combine tools such as prepared statements, output escaping, and HTML allow-list filtering.
Concept
There is no single catch-all function in PHP that can safely solve SQL injection, XSS, and allowed HTML filtering at the same time.
These are different problems, and each requires a different solution.
The three separate concerns
1. SQL injection prevention
SQL injection happens when user input is mixed directly into an SQL query string.
The correct solution is:
- Use prepared statements
- Bind parameters
- Do not manually escape SQL unless absolutely necessary
In modern PHP, this usually means using PDO or MySQLi prepared statements.
2. XSS prevention
XSS happens when unsafe content is displayed in HTML and the browser interprets it as code.
The correct solution is:
- Escape output when rendering HTML
- In PHP, this usually means
htmlspecialchars()
Important: XSS prevention is generally an output problem, not an input problem.
3. Allowing some HTML tags
Sometimes you want to allow formatting tags such as <b>, <i>, or <p>.
This is more complex than simply stripping tags. If you allow HTML, you must make sure:
- only approved tags are allowed
- dangerous attributes like
onclickare removed - dangerous URLs such as
javascript:are blocked
For this, developers often use a dedicated HTML sanitizer such as HTML Purifier.
Why this matters
In real applications, user input can be used in many contexts:
- SQL queries
- HTML pages
- JavaScript strings
- JSON responses
- URLs
- email templates
A string that is safe in one context may be unsafe in another. That is why secure programming uses context-specific handling instead of one global sanitize function.
Key rule
A good mental rule is:
- Validate input based on what your application expects
- Use prepared statements for database queries
- Escape output based on where the data is displayed
- Sanitize HTML with an allow-list tool only when HTML input is intentionally allowed
Mental Model
Think of user input like a package arriving at a building.
- Validation checks whether the package is the right type: is this really an email, age, or comment?
- Prepared statements are like a secure loading dock for the database: the package contents cannot secretly rewrite the delivery instructions.
- Output escaping is like wrapping dangerous items before bringing them into a room: the browser sees text, not executable code.
- HTML sanitizing is like a security checkpoint that allows only approved items through, such as
<b>and<p>, while removing dangerous ones.
The important lesson is that one security guard cannot do every job. Each step protects a different part of the system.
Syntax and Examples
1. Prevent SQL injection with prepared statements
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$name = $_POST['name'] ?? '';
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute(['name' => $name]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
Here, :name is a placeholder. The input is sent separately from the SQL command, so it cannot change the query structure.
2. Prevent XSS with output escaping
<?php
$comment = $_POST['comment'] ?? '';
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
If the user submits:
<script>alert('xss')</script>
The browser will display it as text instead of running it.
3. Allow only plain text by stripping tags
<?php
$comment = $_POST['comment'] ?? '';
$plainTextComment = strip_tags($comment);
This removes HTML tags, but it is not enough when you want to safely allow some HTML.
4. Allow some HTML with a dedicated sanitizer
Using HTML Purifier:
<?php
require_once 'vendor/autoload.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,strong,i,em,a[href],ul,ol,li,br');
$purifier = new HTMLPurifier($config);
$safeHtml = $purifier->purify($_POST['comment'] ?? '');
echo $safeHtml;
This is much safer than using strip_tags() alone because it can also filter dangerous attributes and malformed HTML.
5. Validation example
<?php
$email = $_POST['email'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo 'Invalid email address';
}
Validation checks whether the input matches your expected format. It does not replace SQL protection or XSS protection.
Step by Step Execution
Consider this example:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$comment = $_POST['comment'] ?? '';
$stmt = $pdo->prepare('INSERT INTO comments (body) VALUES (:body)');
$stmt->execute(['body' => $comment]);
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
What happens step by step
1. Read input
$comment = $_POST['comment'] ?? '';
- If the form sent
comment, its value is used. - Otherwise, an empty string is used.
Example input:
Hello <script>alert('bad')</script>
2. Prepare SQL safely
$stmt = $pdo->prepare('INSERT INTO comments (body) VALUES (:body)');
- PHP creates a query template.
:bodyis a placeholder, not raw SQL text.
3. Execute with bound data
$stmt->execute(['body' => $comment]);
- The comment is passed as data.
- Even if it contains quotes or SQL-like text, it does not become part of the SQL command itself.
4. Escape before output
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
<becomes<>becomes>- quotes are also escaped
- the browser displays the text instead of executing it
Result
Stored in the database:
Hello <script>alert('bad')</script>
Displayed in the browser:
Hello <script>alert('bad')</script>
This shows a common secure pattern:
- store raw or validated content
- use prepared statements for the database
- escape when outputting to HTML
Real World Use Cases
Comment systems
Users submit comments that may contain text, punctuation, quotes, and sometimes limited formatting. A typical approach is:
- validate comment length
- store using prepared statements
- escape output for plain text comments
- or sanitize with an HTML allow-list for rich text comments
Blog or CMS editors
An admin or trusted user may be allowed to use tags like <p>, <h2>, and <a>. In this case:
- use a dedicated HTML sanitizer
- restrict tags and attributes
- reject dangerous protocols and event handlers
Search forms
A search term should not be inserted directly into SQL. Instead:
- pass the search text as a bound parameter
- escape the search text when redisplaying it in the page
Profile fields
Fields such as username, bio, and location all need different handling:
- username: validate allowed characters
- bio: maybe allow limited formatting
- location: often plain text only
APIs and admin panels
Even internal tools need safe handling. Admin users can accidentally paste unsafe HTML or malformed data. Security rules should still apply.
Real Codebase Usage
In real PHP projects, developers usually do not create one giant sanitize() function for everything. Instead, they use a few clear patterns.
1. Validate early
Developers check input as soon as it enters the application.
<?php
$title = trim($_POST['title'] ?? '');
if ($title === '') {
throw new InvalidArgumentException('Title is required.');
}
This is often called validation at the boundary.
2. Use guard clauses
Guard clauses stop bad input early.
<?php
$age = $_POST['age'] ?? null;
if (!filter_var($age, FILTER_VALIDATE_INT)) {
die('Age must be a valid integer.');
}
3. Store data safely, then escape on output
A common pattern is:
- validate input shape
- store with prepared statements
- escape when rendering in HTML templates
<?php
function e(string $value): string {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
Then in templates:
<p><?= e($user['name']) ?></p>
4. Use dedicated sanitizers for rich text
For user-generated HTML, real projects often use libraries instead of hand-written regex or strip_tags().
5. Separate rules by field type
A real form may have different rules for each field:
email: validate as emailage: validate as integercomment: plain text or sanitized HTMLuser_id: integer only
6. Centralize helpers, not all logic
Many codebases create small helpers:
db()for database accesse()for HTML escaping- validation functions for common field types
- a service for rich text sanitization
This keeps security consistent without pretending one function solves every context.
Common Mistakes
Mistake 1: Using one function for every security problem
Broken idea:
<?php
$input = addslashes($_POST['input']);
Why it is wrong:
addslashes()is not a complete SQL injection defense- it does not solve XSS
- it depends on database context and is easy to misuse
Use instead:
- prepared statements for SQL
htmlspecialchars()for HTML output
Mistake 2: Escaping input before storing it
Broken code:
<?php
$comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
// store $comment in database
Why it is a problem:
- you mix storage with presentation
- the data is now HTML-specific
- output in another context becomes awkward or wrong
Better approach:
- store raw or validated/sanitized data
- escape when rendering
Mistake 3: Relying on strip_tags() for safe HTML
Broken assumption:
<?php
$safe = strip_tags($html, '<a><p><b>');
Why it is risky:
- allowed tags may still contain dangerous attributes
- malformed HTML can create unexpected results
- it is not a full HTML sanitizer
Use a dedicated allow-list sanitizer for HTML input.
Mistake 4: Building SQL with string concatenation
Broken code:
<?php
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
Why it is dangerous:
- attackers can inject SQL fragments
- even numeric-looking fields should be treated carefully
Safer version:
<?php
$id = $_GET['id'] ?? '';
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
Mistake 5: Forgetting the output context
htmlspecialchars() is for HTML text output. It is not the correct solution for every place.
Examples of other contexts:
- JavaScript strings
- URL parameters
- JSON
- HTML attributes
Always escape for the context where the data is being used.
Mistake 6: Trusting admin input completely
Even trusted users can paste unsafe content accidentally. If HTML is allowed, sanitize it consistently.
Comparisons
| Problem | Wrong or weak approach | Better approach | Why |
|---|---|---|---|
| SQL injection | addslashes() or string concatenation | Prepared statements | Separates SQL code from user data |
| XSS in HTML output | Trying to clean all input globally | htmlspecialchars() on output | Escapes data for the browser context |
| Allowing some HTML | strip_tags() only | HTML Purifier or similar allow-list sanitizer | Removes dangerous tags and attributes safely |
| Checking input format | Blindly accepting everything | Validation with filter_var(), regex, or custom rules | Ensures input matches expected shape |
htmlspecialchars() vs strip_tags()
| Function | Purpose | Good for | Not enough for |
|---|---|---|---|
htmlspecialchars() | Escapes characters for HTML output | Preventing XSS in plain text output | Allowing safe HTML |
strip_tags() | Removes HTML tags | Converting input to plain text | Full HTML sanitization |
Prepared statements vs manual escaping
| Approach | Recommendation | Notes |
|---|---|---|
| Prepared statements | Preferred | Safe, standard, easier to maintain |
| Manual SQL escaping | Avoid when possible | Easy to get wrong and database-specific |
Input validation vs output escaping
| Concept | When it happens | Example |
|---|---|---|
| Validation | Before processing or saving | Check whether email format is valid |
| Output escaping | Right before display | Escape <script> so it shows as text |
Cheat Sheet
Core rules
- There is no single catch-all sanitize function for all security problems.
- Use prepared statements to prevent SQL injection.
- Use
htmlspecialchars()to prevent XSS in HTML output. - Use validation to make sure input matches expected formats.
- If you want to allow some HTML, use an allow-list HTML sanitizer such as HTML Purifier.
Safe patterns
SQL query
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
HTML output
echo htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
Email validation
filter_var($email, FILTER_VALIDATE_EMAIL)
Plain text only
$plain = strip_tags($input);
Important reminders
- Do not use
addslashes()as SQL protection. - Do not escape everything before saving to the database.
- Do not assume
strip_tags()makes HTML safe. - Do not build SQL queries by concatenating user input.
- Escape based on output context.
Handy helper
function e(string $value): string {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
If HTML must be allowed
- allow only required tags
- allow only safe attributes
- block dangerous protocols like
javascript: - use a proven sanitizer library
FAQ
Is there a single PHP function to prevent both SQL injection and XSS?
No. SQL injection and XSS are different problems. Use prepared statements for SQL and output escaping for HTML.
Should I sanitize input before saving it to the database?
Usually, validate it before saving, but do not HTML-escape everything before storage. Escape when outputting the data.
Is htmlspecialchars() enough to stop XSS?
It is the standard solution for plain text in HTML output. If you want to allow HTML, use a proper HTML sanitizer instead.
Is strip_tags() safe for allowing a few tags?
Not by itself. It can remove tags, but it does not fully protect against dangerous attributes or complex HTML payloads.
What should I use in PHP for SQL injection prevention?
Use PDO or MySQLi prepared statements with bound parameters.
When should I use filter_var()?
Use it for validation, such as checking emails, integers, and URLs. It does not replace SQL protection or XSS protection.
Can I trust admin or logged-in users more?
You can trust them more for permissions, but their input should still be handled safely. Mistakes and unsafe pasted content can still happen.
Mini Project
Description
Build a simple PHP comment form that accepts a user's name and comment, stores the data safely, and displays it without allowing XSS. The project demonstrates the correct separation of concerns: validation for expected input, prepared statements for database safety, and output escaping for HTML rendering.
Goal
Create a secure comment submission flow in PHP that prevents SQL injection and safely displays user content.
Requirements
- Accept a name and a comment from an HTML form.
- Reject empty values after trimming whitespace.
- Insert the comment into a database using a prepared statement.
- Display saved comments using safe HTML escaping.
- Keep comments as plain text only for this version.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.