WordPress Media Upload Example: Handle Plugin File Uploads Safely
Handle WordPress plugin file uploads safely with nonce checks, capabilities, MIME restrictions, media_handle_upload, attachment metadata, and private-file warnings.
Published
April 21, 2026
Reading Time
3 min read
Updated
April 21, 2026

Hardening Notes
Baselines, access reduction, and default settings that stand up in production.
Best For
Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.
Primary Topics
Editorial Focus
Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 21, 2026.
Full Report
Last reviewed: April 21, 2026
File upload features are high-risk because they accept data from a user’s machine and move it into the WordPress environment. A plugin upload form needs more than an input field. It needs nonce verification, capability checks, file type restrictions, error handling, and a clear decision about whether the file becomes a Media Library attachment.
This article shows a safe WordPress media upload pattern for plugin code using media_handle_upload().
When this applies
Use this pattern when a logged-in user uploads an image or document that should become a WordPress attachment. If the file is only temporary import data, use a stricter temporary upload workflow and delete the file after processing.
Upload form
<form method="post" enctype="multipart/form-data">
<input type="file" name="vulnwp_upload" accept="image/jpeg,image/png">
<?php wp_nonce_field( 'vulnwp_upload_file', 'vulnwp_upload_nonce' ); ?>
<button type="submit" name="vulnwp_upload_submit" value="1">
<?php esc_html_e( 'Upload file', 'vulnwp-upload' ); ?>
</button>
</form>
Handle the upload
add_action( 'admin_init', 'vulnwp_handle_admin_upload' );
function vulnwp_handle_admin_upload() {
if ( empty( $_POST['vulnwp_upload_submit'] ) ) {
return;
}
if (
empty( $_POST['vulnwp_upload_nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['vulnwp_upload_nonce'] ) ), 'vulnwp_upload_file' )
) {
wp_die( esc_html__( 'Security check failed.', 'vulnwp-upload' ) );
}
if ( ! current_user_can( 'upload_files' ) ) {
wp_die( esc_html__( 'You are not allowed to upload files.', 'vulnwp-upload' ) );
}
if ( empty( $_FILES['vulnwp_upload']['name'] ) ) {
return;
}
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
$attachment_id = media_handle_upload(
'vulnwp_upload',
0,
array(),
array(
'mimes' => array(
'jpg|jpeg' => 'image/jpeg',
'png' => 'image/png',
),
)
);
if ( is_wp_error( $attachment_id ) ) {
wp_die( esc_html( $attachment_id->get_error_message() ) );
}
update_post_meta( $attachment_id, '_vulnwp_uploaded_by_plugin', 1 );
}
This handler verifies intent, checks permission, loads the required WordPress media files, restricts allowed MIME types, and stores a marker meta value on successful uploads.
File type and size rules
Do not trust the browser’s accept attribute. It helps users choose the right file, but server-side validation still matters. WordPress performs file type checks, and the plugin can narrow allowed MIME types with the mimes override.
If the upload has a business-specific size limit, check $_FILES['vulnwp_upload']['size'] before processing and return a useful error. Do not rely only on PHP’s global upload limits.
Attachment metadata
If the file becomes a Media Library attachment, add useful title, alt text, caption, or plugin marker metadata after upload. That makes the asset easier to audit later.
if ( ! is_wp_error( $attachment_id ) ) {
update_post_meta(
$attachment_id,
'_wp_attachment_image_alt',
__( 'Uploaded review image', 'vulnwp-upload' )
);
}
Private files need a different design
The Media Library is for files that WordPress can serve as uploads. If the file contains private exports, customer data, logs, or security evidence, do not place it in a public uploads path without a controlled access layer. Use a private storage design and serve downloads only after authorization.
Common mistakes
- No nonce. Upload forms need CSRF protection.
- No capability check. Only authorized users should upload files.
- Allowing every MIME type. Restrict uploads to the real feature requirement.
- Printing raw errors publicly. Keep errors useful but not revealing.
- Leaving temporary files behind. Delete temporary import data after processing.
Production checklist
- Use
enctype="multipart/form-data"on the form. - Verify nonce and capability before processing files.
- Load WordPress media includes when outside normal media screens.
- Restrict MIME types and enforce business size limits.
- Handle
WP_Errorresults. - Document where uploaded files are stored and who can access them.
Related reading
This pairs with the file permissions checklist and the nonce example.


