Detailed security measures for the file upload system. PRO feature.
MIME Validation
Server-side validation checks the actual file MIME type, not just the extension:
php
$allowed_types = [
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
'application/pdf', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain', 'text/csv',
];
$file_type = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] );
if ( ! in_array( $file_type['type'], $allowed_types, true ) ) {
wp_send_json_error( [ 'message' => 'This file type is not allowed.' ] );
}Size Limit
Maximum file size is 10 MB per file:
php
$max_size = 10 * 1024 * 1024;
if ( $file['size'] > $max_size ) {
wp_send_json_error( [ 'message' => 'File size exceeds the 10MB limit.' ] );
}Upload Processing Flow
php
// 1. Validate MIME type
// 2. Check file size
// 3. Move the file into uploads/formforge-private/YYYY/MM/
// 4. Store private metadata in submission data
$stored_file = [
'token' => 'random-download-token',
'name' => 'resume.pdf',
'stored_name' => 'uuid.pdf',
'relative_path' => 'formforge-private/2026/05/uuid.pdf',
'mime' => 'application/pdf',
'size' => 12345,
'field_id' => 'resume',
];Preventing PHP File Execution
WordPress’s wp_check_filetype_and_ext() prevents .php files renamed to .jpg from being accepted. The MIME type check catches the mismatch between extension and actual content.
Accessing Uploaded Files
php
$data = json_decode( $sub->data, true );
$file = $data['field_resume']['file'] ?? null;
if ( $file ) {
echo esc_html( $file['name'] );
// Admin downloads go through:
// admin-ajax.php?action=formforge_download_file&submission_id=...&field=...&token=...&_wpnonce=...
}Do not build public wp-content/uploads/... URLs for Form Forge file uploads. The stored relative_path is intentionally private and should only be served through the admin download handler.
—