sanitize_filename()
function sanitize_filename(string $filename, bool $transliteration = true, int $max_length = 200): string
Description
Sanitizes a filename for safe storage and usage on the filesystem and web. This function removes or replaces special characters, handles international characters through transliteration, preserves file extensions, and prevents common security issues.
The function leverages url_title() internally to handle the heavy lifting of character conversion and normalization.
Security Note: This function throws an InvalidArgumentException if the filename contains null bytes, which can be used in directory traversal attacks.
Parameters
| Parameter |
Type |
Description |
Default |
Required |
| $filename |
string |
The filename to sanitize (may include file extension). |
N/A |
Yes |
| $transliteration |
bool |
Whether to transliterate international characters to ASCII equivalents. Requires the 'intl' PHP extension. |
true |
No |
| $max_length |
int |
Maximum length for the base filename (excluding extension). Helps prevent filesystem issues. |
200 |
No |
Return Value
| Type |
Description |
| string |
The sanitized filename with preserved and normalized extension. |
What Gets Cleaned
The function handles multiple types of problematic characters:
- International characters: Transliterates to ASCII (e.g., "Москва" → "moskva", "café" → "cafe")
- Special characters: Removes or converts to dashes (e.g., "@#$%" → "")
- Whitespace: Converts to dashes (e.g., "my file.jpg" → "my-file.jpg")
- Multiple spaces/dashes: Collapses to single dashes
- Parentheses and brackets: Removes them (e.g., "photo (1).jpg" → "photo-1.jpg")
- File extensions: Preserves and normalizes to lowercase alphanumeric only
Example #1
Basic filename sanitization:
$clean = sanitize_filename('My Photo (1).jpg');
echo $clean;
// Output: my-photo-1.jpg
Example #2
The example above shows how to sanitize a user-uploaded filename before storing it on the server.
public function upload_document(): void {
$user_id = segment(3, 'int');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
$uploaded_file = $_FILES['document'];
// Sanitize the original filename
$original_name = $uploaded_file['name'];
$safe_filename = sanitize_filename($original_name);
// Create user-specific directory if it doesn't exist
$upload_dir = 'modules/documents/uploads/' . $user_id;
if (!$this->file->exists($upload_dir)) {
$this->file->create_directory($upload_dir, 0755);
}
$destination = $upload_dir . '/' . $safe_filename;
// Move uploaded file
if (move_uploaded_file($uploaded_file['tmp_name'], $destination)) {
// Store document record in database
$data['user_id'] = $user_id;
$data['original_filename'] = $original_name;
$data['stored_filename'] = $safe_filename;
$data['file_path'] = $destination;
$data['upload_date'] = time();
$this->model->insert($data, 'documents');
set_flashdata('Document uploaded successfully');
redirect('documents/manage/' . $user_id);
}
}
}
Example #3
The example above demonstrates handling international filenames with transliteration enabled.
public function process_international_upload(): void {
$user_id = segment(3, 'int');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
$uploaded_file = $_FILES['document'];
$original_name = $uploaded_file['name'];
// Sanitize with transliteration (default behavior)
$safe_filename = sanitize_filename($original_name, true);
$upload_dir = 'modules/documents/international';
if (!$this->file->exists($upload_dir)) {
$this->file->create_directory($upload_dir, 0755);
}
$destination = $upload_dir . '/' . $safe_filename;
if (move_uploaded_file($uploaded_file['tmp_name'], $destination)) {
$data['user_id'] = $user_id;
$data['original_filename'] = $original_name;
$data['stored_filename'] = $safe_filename;
$data['file_path'] = $destination;
$this->model->insert($data, 'documents');
set_flashdata('Document uploaded: ' . $original_name);
redirect('documents/manage/' . $user_id);
}
}
}
Transliteration Examples: With transliteration enabled (requires PHP 'intl' extension):
- "Москва.jpg" becomes "moskva.jpg"
- "café résumé.pdf" becomes "cafe-resume.pdf"
- "北京_beijing.jpg" becomes "bei-jing-beijing.jpg"
Without transliteration, international characters are simply removed, which may result in less readable filenames.
Example #4
The example above shows handling filenames that may conflict with existing files by adding unique identifiers.
public function upload_with_conflict_handling(): void {
$user_id = segment(3, 'int');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
$uploaded_file = $_FILES['document'];
$original_name = $uploaded_file['name'];
// Sanitize the filename
$safe_filename = sanitize_filename($original_name);
$upload_dir = 'modules/documents/uploads';
if (!$this->file->exists($upload_dir)) {
$this->file->create_directory($upload_dir, 0755);
}
$destination = $upload_dir . '/' . $safe_filename;
// Check if file already exists
if ($this->file->exists($destination)) {
// Extract name and extension
$info = pathinfo($safe_filename);
$name = $info['filename'];
$extension = isset($info['extension']) ? '.' . $info['extension'] : '';
// Add timestamp to make filename unique
$timestamp = time();
$safe_filename = $name . '_' . $timestamp . $extension;
$destination = $upload_dir . '/' . $safe_filename;
}
// Move uploaded file
if (move_uploaded_file($uploaded_file['tmp_name'], $destination)) {
$data['user_id'] = $user_id;
$data['original_filename'] = $original_name;
$data['stored_filename'] = $safe_filename;
$data['file_path'] = $destination;
$data['upload_date'] = time();
$this->model->insert($data, 'documents');
set_flashdata('Document uploaded successfully');
redirect('documents/manage/' . $user_id);
}
}
}
Preventing Overwrites: Always check if a file exists before saving uploads. Add unique identifiers (timestamps, user IDs, or UUIDs) to prevent accidentally overwriting existing files. The sanitize_filename() function doesn't handle conflicts—that's your responsibility.
Example #5
The example above demonstrates using custom length limits for specific use cases.
public function create_thumbnail_filename(): void {
$document_id = segment(3, 'int');
// Get original document
$document = $this->db->get_where($document_id, 'documents');
if ($document === false) {
redirect('documents/not_found');
}
// Create a short thumbnail filename based on original
// Use shorter max_length for thumbnail names
$original_filename = $document->original_filename;
$safe_name = sanitize_filename($original_filename, true, 50);
// Remove extension and add thumbnail suffix
$info = pathinfo($safe_name);
$base_name = $info['filename'];
$thumbnail_filename = $base_name . '_thumb.jpg';
// Generate thumbnail (pseudo-code)
$source_path = $document->file_path;
$thumb_dir = 'modules/documents/thumbnails';
if (!$this->file->exists($thumb_dir)) {
$this->file->create_directory($thumb_dir, 0755);
}
$thumb_path = $thumb_dir . '/' . $thumbnail_filename;
// Create thumbnail image
$this->create_thumbnail_image($source_path, $thumb_path, 150, 150);
// Update database record
$update_data['thumbnail_path'] = $thumb_path;
$this->model->update($document_id, $update_data, 'documents');
set_flashdata('Thumbnail created successfully');
redirect('documents/show/' . $document_id);
}
private function create_thumbnail_image(string $source, string $dest, int $width, int $height): void {
// Thumbnail generation logic here
// This is a placeholder for the actual implementation
}
Length Limits: Most filesystems support up to 255 bytes for filenames, but the default $max_length of 200 characters leaves room for extensions and gives you flexibility to add suffixes like "_thumb" or timestamps. Adjust this parameter based on your specific needs—shorter for thumbnails, longer for user-facing document names.