Title: media_handle_upload
Published: April 25, 2014
Last modified: August 20, 2026

---

# media_handle_upload( string $file_id, int $post_id, array $post_data = array(), array $overrides = array('test_form' => false) ): int|󠀁[WP_Error](https://developer.wordpress.org/reference/classes/wp_error/)󠁿

## In this article

 * [Parameters](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#parameters)
 * [Return](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#return)
 * [Source](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#source)
 * [Related](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#changelog)
 * [User Contributed Notes](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#user-contributed-notes)

[ Back to top](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#wp--skip-link--target)

Saves a file submitted from a POST request and create an attachment post for it.

## 󠀁[Parameters](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#parameters)󠁿

 `$file_id`stringrequired

Index of the `$_FILES` array that the file was sent.

`$post_id`intrequired

The post ID of a post to attach the media item to. Required, but can be set to 0,
creating a media item that has no relationship to a post.

`$post_data`arrayoptional

Overwrite some of the attachment.

Default:`array()`

`$overrides`arrayoptional

Override the [wp_handle_upload()](https://developer.wordpress.org/reference/functions/wp_handle_upload/)
behavior.

More Arguments from wp_handle_upload( … $overrides )

An array of override parameters for this file, or boolean false if none are provided.

 * `upload_error_handler` callable
 * Function to call when there is an error during the upload process.
    See [wp_handle_upload_error()](https://developer.wordpress.org/reference/functions/wp_handle_upload_error/).
 * `unique_filename_callback` callable
 * Function to call when determining a unique file name for the file.
    See [wp_unique_filename()](https://developer.wordpress.org/reference/functions/wp_unique_filename/).
 * `upload_error_strings` string[]
 * The strings that describe the error indicated in `$_FILES[{form field}]['error']`.
 * `test_form` bool
 * Whether to test that the `$_POST['action']` parameter is as expected.
 * `test_size` bool
 * Whether to test that the file size is greater than zero bytes.
 * `test_type` bool
 * Whether to test that the mime type of the file is as expected.
 * `mimes` string[]
 * Array of allowed mime types keyed by their file extension regex.

Default:`array('test_form' => false)`

## 󠀁[Return](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#return)󠁿

 int|[WP_Error](https://developer.wordpress.org/reference/classes/wp_error/) ID 
of the attachment or a [WP_Error](https://developer.wordpress.org/reference/classes/wp_error/)
object on failure.

## 󠀁[Source](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#source)󠁿

    ```php
    function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrides = array( 'test_form' => false ) ) {
    	$time = current_time( 'mysql' );
    	$post = get_post( $post_id );

    	if ( $post ) {
    		// The post date doesn't usually matter for pages, so don't backdate this upload.
    		if ( 'page' !== $post->post_type && substr( $post->post_date, 0, 4 ) > 0 ) {
    			$time = $post->post_date;
    		}
    	}

    	$file = wp_handle_upload( $_FILES[ $file_id ], $overrides, $time );

    	if ( isset( $file['error'] ) ) {
    		return new WP_Error( 'upload_error', $file['error'] );
    	}

    	$name = $_FILES[ $file_id ]['name'];
    	$ext  = pathinfo( $name, PATHINFO_EXTENSION );
    	$name = wp_basename( $name, ".$ext" );

    	$url     = $file['url'];
    	$type    = $file['type'];
    	$file    = $file['file'];
    	$title   = sanitize_text_field( $name );
    	$content = '';
    	$excerpt = '';
    	$alt     = '';

    	if ( preg_match( '#^audio#', $type ) ) {
    		$meta = wp_read_audio_metadata( $file );

    		if ( ! empty( $meta['title'] ) ) {
    			$title = $meta['title'];
    		}

    		if ( ! empty( $title ) ) {

    			if ( ! empty( $meta['album'] ) && ! empty( $meta['artist'] ) ) {
    				/* translators: 1: Audio track title, 2: Album title, 3: Artist name. */
    				$content .= sprintf( __( '"%1$s" from %2$s by %3$s.' ), $title, $meta['album'], $meta['artist'] );
    			} elseif ( ! empty( $meta['album'] ) ) {
    				/* translators: 1: Audio track title, 2: Album title. */
    				$content .= sprintf( __( '"%1$s" from %2$s.' ), $title, $meta['album'] );
    			} elseif ( ! empty( $meta['artist'] ) ) {
    				/* translators: 1: Audio track title, 2: Artist name. */
    				$content .= sprintf( __( '"%1$s" by %2$s.' ), $title, $meta['artist'] );
    			} else {
    				/* translators: %s: Audio track title. */
    				$content .= sprintf( __( '"%s".' ), $title );
    			}
    		} elseif ( ! empty( $meta['album'] ) ) {

    			if ( ! empty( $meta['artist'] ) ) {
    				/* translators: 1: Audio album title, 2: Artist name. */
    				$content .= sprintf( __( '%1$s by %2$s.' ), $meta['album'], $meta['artist'] );
    			} else {
    				$content .= $meta['album'] . '.';
    			}
    		} elseif ( ! empty( $meta['artist'] ) ) {

    			$content .= $meta['artist'] . '.';

    		}

    		if ( ! empty( $meta['year'] ) ) {
    			/* translators: Audio file track information. %d: Year of audio track release. */
    			$content .= ' ' . sprintf( __( 'Released: %d.' ), $meta['year'] );
    		}

    		if ( ! empty( $meta['track_number'] ) ) {
    			$track_number = explode( '/', $meta['track_number'] );

    			if ( is_numeric( $track_number[0] ) ) {
    				if ( isset( $track_number[1] ) && is_numeric( $track_number[1] ) ) {
    					$content .= ' ' . sprintf(
    						/* translators: Audio file track information. 1: Audio track number, 2: Total audio tracks. */
    						__( 'Track %1$s of %2$s.' ),
    						number_format_i18n( $track_number[0] ),
    						number_format_i18n( $track_number[1] )
    					);
    				} else {
    					$content .= ' ' . sprintf(
    						/* translators: Audio file track information. %s: Audio track number. */
    						__( 'Track %s.' ),
    						number_format_i18n( $track_number[0] )
    					);
    				}
    			}
    		}

    		if ( ! empty( $meta['genre'] ) ) {
    			/* translators: Audio file genre information. %s: Audio genre name. */
    			$content .= ' ' . sprintf( __( 'Genre: %s.' ), $meta['genre'] );
    		}

    		// Use image exif/iptc data for title and caption defaults if possible.
    	} elseif ( str_starts_with( $type, 'image/' ) ) {
    		$image_meta = wp_read_image_metadata( $file );

    		if ( $image_meta ) {
    			if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
    				$title = $image_meta['title'];
    			}

    			if ( trim( $image_meta['caption'] ) ) {
    				$excerpt = $image_meta['caption'];
    			}

    			if ( trim( $image_meta['alt'] ) ) {
    				$alt = $image_meta['alt'];
    			}
    		}
    	}

    	// Construct the attachment array.
    	$attachment = array_merge(
    		array(
    			'post_mime_type' => $type,
    			'guid'           => $url,
    			'post_parent'    => $post_id,
    			'post_title'     => $title,
    			'post_content'   => $content,
    			'post_excerpt'   => $excerpt,
    		),
    		$post_data
    	);

    	// This should never be set as it would then overwrite an existing attachment.
    	unset( $attachment['ID'] );

    	// Save the data.
    	$attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );

    	if ( trim( $alt ) ) {
    		update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $alt ) );
    	}

    	if ( ! is_wp_error( $attachment_id ) ) {
    		/*
    		 * Set a custom header with the attachment_id.
    		 * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
    		 */
    		if ( ! headers_sent() ) {
    			header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
    		}

    		/*
    		 * The image sub-sizes are created during wp_generate_attachment_metadata().
    		 * This is generally slow and may cause timeouts or out of memory errors.
    		 */
    		wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
    	}

    	return $attachment_id;
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-admin/includes/media.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/7.1/src/wp-admin/includes/media.php#L296)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/7.1/src/wp-admin/includes/media.php#L296-L451)

## 󠀁[Related](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#related)󠁿

| Uses | Description | 
| [wp_read_image_metadata()](https://developer.wordpress.org/reference/functions/wp_read_image_metadata/)`wp-admin/includes/image.php` |

Gets extended image metadata, exif or iptc as available.

  | 
| [wp_generate_attachment_metadata()](https://developer.wordpress.org/reference/functions/wp_generate_attachment_metadata/)`wp-admin/includes/image.php` |

Generates attachment meta data and create image sub-sizes for images.

  | 
| [wp_read_audio_metadata()](https://developer.wordpress.org/reference/functions/wp_read_audio_metadata/)`wp-admin/includes/media.php` |

Retrieves metadata from an audio file’s ID3 tags.

  | 
| [wp_handle_upload()](https://developer.wordpress.org/reference/functions/wp_handle_upload/)`wp-admin/includes/file.php` |

Wrapper for [_wp_handle_upload()](https://developer.wordpress.org/reference/functions/_wp_handle_upload/) .

  | 
| [sanitize_title()](https://developer.wordpress.org/reference/functions/sanitize_title/)`wp-includes/formatting.php` |

Sanitizes a string into a slug, which can be used in URLs or HTML attributes.

  | 
| [current_time()](https://developer.wordpress.org/reference/functions/current_time/)`wp-includes/functions.php` |

Retrieves the current time based on specified type.

  | 
| [wp_insert_attachment()](https://developer.wordpress.org/reference/functions/wp_insert_attachment/)`wp-includes/post.php` |

Inserts an attachment.

  | 
| [wp_update_attachment_metadata()](https://developer.wordpress.org/reference/functions/wp_update_attachment_metadata/)`wp-includes/post.php` |

Updates metadata for an attachment.

  | 
| [update_post_meta()](https://developer.wordpress.org/reference/functions/update_post_meta/)`wp-includes/post.php` |

Updates a post meta field based on the given post ID.

  | 
| [__()](https://developer.wordpress.org/reference/functions/__/)`wp-includes/l10n.php` |

Retrieves the translation of $text.

  | 
| [wp_basename()](https://developer.wordpress.org/reference/functions/wp_basename/)`wp-includes/formatting.php` |

i18n-friendly version of basename().

  | 
| [sanitize_text_field()](https://developer.wordpress.org/reference/functions/sanitize_text_field/)`wp-includes/formatting.php` |

Sanitizes a string from user input or from the database.

  | 
| [number_format_i18n()](https://developer.wordpress.org/reference/functions/number_format_i18n/)`wp-includes/functions.php` |

Converts float number to format based on the locale.

  | 
| [get_post()](https://developer.wordpress.org/reference/functions/get_post/)`wp-includes/post.php` |

Retrieves post data given a post ID or post object.

  | 
| [is_wp_error()](https://developer.wordpress.org/reference/functions/is_wp_error/)`wp-includes/load.php` |

Checks whether the given variable is a WordPress Error.

  | 
| [WP_Error::__construct()](https://developer.wordpress.org/reference/classes/wp_error/__construct/)`wp-includes/class-wp-error.php` |

Initializes the error.

  |

[Show 11 more](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#)

| Used by | Description | 
| [wp_media_upload_handler()](https://developer.wordpress.org/reference/functions/wp_media_upload_handler/)`wp-admin/includes/media.php` |

Handles the process of uploading media.

  | 
| [wp_ajax_upload_attachment()](https://developer.wordpress.org/reference/functions/wp_ajax_upload_attachment/)`wp-admin/includes/ajax-actions.php` |

Handles uploading attachments via AJAX.

  |

## 󠀁[Changelog](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#changelog)󠁿

| Version | Description | 
| [2.5.0](https://developer.wordpress.org/reference/since/2.5.0/) | Introduced. |

## 󠀁[User Contributed Notes](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#user-contributed-notes)󠁿

 1.   [Skip to note 3 content](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#comment-content-534)
 2.    [Codex](https://profiles.wordpress.org/codex/)  [  11 years ago  ](https://developer.wordpress.org/reference/functions/media_handle_upload/#comment-534)
 3.  [You must log in to vote on the helpfulness of this note](https://login.wordpress.org?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F%23comment-534)
     Vote results for this note: 11[You must log in to vote on the helpfulness of this note](https://login.wordpress.org?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F%23comment-534)
 4.  Upload an attachment from a form on the front end of the site.
 5.  The upload form might look like this:
 6.      ```php
         <form id="featured_upload" method="post" action="#" enctype="multipart/form-data">
         	<input type="file" name="my_image_upload" id="my_image_upload"  multiple="false" />
         	<input type="hidden" name="post_id" id="post_id" value="55" />
         	<?php wp_nonce_field( 'my_image_upload', 'my_image_upload_nonce' ); ?>
         	<input id="submit_my_image_upload" name="submit_my_image_upload" type="submit" value="Upload" />
         </form>
         ```
     
 7.  The code to save the attachment:
 8.      ```php
         <?php
     
         // Check that the nonce is valid, and the user can edit this post.
         if ( 
         	isset( $_POST['my_image_upload_nonce'], $_POST['post_id'] ) 
         	&& wp_verify_nonce( $_POST['my_image_upload_nonce'], 'my_image_upload' )
         	&& current_user_can( 'edit_post', $_POST['post_id'] )
         ) {
         	// The nonce was valid and the user has the capabilities, it is safe to continue.
     
         	// These files need to be included as dependencies when on the front end.
         	require_once( ABSPATH . 'wp-admin/includes/image.php' );
         	require_once( ABSPATH . 'wp-admin/includes/file.php' );
         	require_once( ABSPATH . 'wp-admin/includes/media.php' );
     
         	// Let WordPress handle the upload.
         	// Remember, 'my_image_upload' is the name of our file input in our form above.
         	$attachment_id = media_handle_upload( 'my_image_upload', $_POST['post_id'] );
     
         	if ( is_wp_error( $attachment_id ) ) {
         		// There was an error uploading the image.
         	} else {
         		// The image was uploaded successfully!
         	}
     
         } else {
     
         	// The security check failed, maybe show the user an error.
         }
         ```
     
 9.   [Log in to add feedback](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F%3Freplytocom%3D534%23feedback-editor-534)
 10.  [Skip to note 4 content](https://developer.wordpress.org/reference/functions/media_handle_upload/?output_format=md#comment-content-6301)
 11.   [M@X](https://profiles.wordpress.org/cityz/)  [  4 years ago  ](https://developer.wordpress.org/reference/functions/media_handle_upload/#comment-6301)
 12. [You must log in to vote on the helpfulness of this note](https://login.wordpress.org?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F%23comment-6301)
     Vote results for this note: 1[You must log in to vote on the helpfulness of this note](https://login.wordpress.org?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F%23comment-6301)
 13. Be sure you use attribute **enctype=”multipart/form-data”** at your form.
      If 
     you missed it, the $_FILES would be empty.
 14.  [Log in to add feedback](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F%3Freplytocom%3D6301%23feedback-editor-6301)

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fmedia_handle_upload%2F)
before being able to contribute a note or feedback.