This post was updated on March 22, 2013. Please review the code changes below.

WordPress makes it pretty easy to capture additional information with your posts through the use of custom meta boxes. Adding checkboxes, select options, textareas, radio buttons, and other input elements are easy.

But giving users the ability to upload files from a post screen requires a little more work.

If you’re not interested in tapping into the Media Uploader, then here’s how you can programmatically create a WordPress upload meta box.

TL;DR: All of this code is available in this GitHub repository as a plugin that can be downloaded and installed in your copy of WordPress.

Though there is some additional code, such as JavaScript and localization files, the bulk of the plugin is as follows:

Plugin Name: WordPress Upload Custom Meta Box 
Plugin URI:
Description: An example plugin for how to include a metabox for attaching files to your WordPress posts outside of the media uploader.
Version: 1.0
Author: Tom McFarlin
Author URI:
Author Email:

  Copyright 2012 - 2013 Tom McFarlin (

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2, as 
  published by the Free Software Foundation.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

if( ! defined( 'UMB_VERSION' ) ) {
	define( 'UMB_VERSION', 1.0 );
} // end if

class Upload_Meta_Box {

	 * Attributes

	 // Represents the nonce value used to save the post media
	 private $nonce = 'wp_upm_media_nonce';

	 * Constructor

	  * Initializes localiztion, sets up JavaScript, and displays the meta box for saving the file
	  * information.
	 public function __construct() {
	 	// Localization, Styles, and JavaScript
	 	add_action( 'init', array( $this, 'plugin_textdomain' ) );
	 	add_action( 'admin_enqueue_scripts', array( $this, 'register_admin_scripts' ) );
	 	// Setup the meta box hooks
	 	add_action( 'add_meta_boxes', array( $this, 'add_file_meta_box' ) );
	 	add_action( 'save_post', array( $this, 'save_custom_meta_data' ) );

	 } // end construct
	 * Localization, Styles, and JavaScript
	 * Defines the text domain for localization.
	public function plugin_textdomain() {
		load_plugin_textdomain( 'umb', false, dirname( plugin_basename( __FILE__ ) ) . '/lang' );
	} // end plugin_textdomain
	 * Addings the admin JavaScript
	public function register_admin_scripts() {
		wp_enqueue_script( 'umb-admin', plugins_url( 'WordPress-Upload-Meta-Box/js/admin.js' ), array( 'jquery'), UMB_VERSION );
	} // end register_scripts
	 * Hooks
	 * Introduces the file meta box for uploading the file to this post.
	public function add_file_meta_box() {
			__( 'Media', 'umb' ),
			array( $this, 'post_media_display' ),
	} // add_file_meta_box
	 * Adds the file input box for the post meta data.
	 * @param		object	$post	The post to which this information is going to be saved.
	public function post_media_display( $post ) {
		wp_nonce_field( plugin_basename( __FILE__ ), $this->nonce );
		$html = '<input id="post_media" type="file" name="post_media" value="" size="25" />';
		$html .= '<p class="description">';
		if( '' == get_post_meta( $post->ID, 'umb_file', true ) ) {
			$html .= __( 'You have no file attached to this post.', 'umb' );
		} else {
			$html .= get_post_meta( $post->ID, 'umb_file', true );
		} // end if
		$html .= '</p><!-- /.description -->';
		echo $html;
	} // end post_media
	 * Determines whether or not the current user has the ability to save meta data associated with this post.
	 * @param		int		$post_id	The ID of the post being save
	 * @param		bool				Whether or not the user has the ability to save this post.
	public function save_custom_meta_data( $post_id ) {
		// First, make sure the user can save the post
		if( $this->user_can_save( $post_id, $this->nonce ) ) { 

			// If the user uploaded an image, let's upload it to the server
			if( ! empty( $_FILES ) && isset( $_FILES['post_media'] ) ) {
				// Upload the goal image to the uploads directory, resize the image, then upload the resized version
				$goal_image_file = wp_upload_bits( $_FILES['post_media']['name'], null, wp_remote_get( $_FILES['post_media']['tmp_name'] ) );

				// Set post meta about this image. Need the comment ID and need the path.
				if( false == $goal_image_file['error'] ) {
					// Since we've already added the key for this, we'll just update it with the file.
					update_post_meta( $post_id, 'umb_file', $goal_image_file['url'] );
				} // end if/else

			} // end if
		} // end if
	} // end update_data

	 * Helper Functions

	 * Determines whether or not the current user has the ability to save meta data associated with this post.
	 * @param		int		$post_id	The ID of the post being save
	 * @param		bool				Whether or not the user has the ability to save this post.
	function user_can_save( $post_id, $nonce ) {
	    $is_autosave = wp_is_post_autosave( $post_id );
	    $is_revision = wp_is_post_revision( $post_id );
	    $is_valid_nonce = ( isset( $_POST[ $nonce ] ) && wp_verify_nonce( $_POST[ $nonce ], plugin_basename( __FILE__ ) ) );
	    // Return true if the user is able to save; otherwise, false.
	    return ! ( $is_autosave || $is_revision ) && $is_valid_nonce;
	} // end user_can_save

} // end class
$GLOBALS['upload-meta-box'] = new Upload_Meta_Box();

If you want to reuse this code for different custom post types, you would have to update the administrator’s JavaScript file to check for the existance of another element.


Join the conversation! 44 Comments

  1. Thank you for the tutorial. It actually worked exactly as advertised and was so nice to find such a nice little script without the bloat that does a job really well.

    I have one request which for the life of me I can’t figure out. Hoping you might be able to help.

    I am looking to do the same thing as this tutorial with one change though. Instead of having the ‘upload’ button, I am trying to find a way of getting the media_button() input there.

    Talking about the same little ‘insert media’ button that you get above the ‘post content’ area when writing a new post. *between the title and the editor*

    I actually use another plugin for inserting kaltura video into my posts. The way their plugin works is that they added a new button next to the ‘add media’ button that WordPress uses.


    By any chance have you done this before, or seen a tutorial on how to access the media_button in a custom field?

    thanks for your time

  2. Thank you, works beautifully! One small detail: any way to have the file name remain in the form field after upload?

    • Currently, no. You wouldn’t be able to display the name of the file in the input box because of browser limitations. If it were to happen after upload, it’s possible, but it’d need some customization – I’m not sure that’s something that’s universally applicable to everyone who uses the plugin.

      If you’re interested in this customization, let me know!.

  3. Hi, me again! Had one more question – just noticed that if that no file is uploaded, there is an automatic error…any way (surely there is :)) to make the upload optional ?

    cheers, and thanks again for sharing this!

  4. Hi, me again – the plugin was working great, and then just suddenly stopped. Impossible to save any files uploaded. Haven’t made any significant changes since – at a bit of a loss…any idea what might cause a conflict?

    many thanks!!

    • Hey Jenny,

      I spent sometime looking and testing the plugin this morning. I wasn’t able to reproduce the problem :(.

      I did, however, implement a couple of very, very minor changes. If you download the latest version it may help, but I’m skeptical.

      The only other thing that I can think of is that it another plugin may be causing a conflict, though if you’ve not changed anything I’m not sure what else to suggest :/.

  5. PS & for info: zip download link isn’t working on github

  6. Quick question – is there a specific function used to call the file, as there is with a post thumbnail or a metadata string?

    • Can you clarify your question just a little bit? Are you looking for a function to call in the context of your theme after the above code has been implemented, or something else?

      • Yes that’s exactly what I’m looking for. Some way to call the file within the theme. For example, I’d want to use this for a restaurant website where menus are a custom post type, and each menu has an associated PDF version. Is there a function I can use in the post template to call the PDF version once I’ve uploaded it using this plugin? And thanks for replying so quickly!

  7. Hi,
    I’ve installed this plugin in 3.6.1, selected a file but nothing happens. In latest Firefox

    “You have no file attached to this post.” this all I can see

  8. Is this compatible with wordpress 3.8.1? I have just downloaded the latest version of the plugin and installed it but it doesn’t seem to save the file?

    The module appears in the post admin and you can click to upload but when saving it still says You have no file attached to this post.

    Am I doing something wrong?

    I have tried with the default twenty fourteen theme as well as my own custom theme but its the same result…. I have also de-activated all plugins apart from this one.

    any help would be appreciated

    • Honestly, I’m not sure, John — I haven’t taken a look at the project since 3.8.1 has been released (or really, since 3.8 was released).

      My best advice would be to setup a couple of debug statements throughout the code to see where something is failing (or where it’s not behaving correctly) and then go from there.

  9. Hi, im having a problem to activate the plugin, displays this error:

    Warning: Cannot modify header information – headers already sent by (output started at /home/notiluca/www/wp-content/themes/twentyeleven/functions.php:775) in /home/notiluca/www/wp-includes/pluggable.php on line 876

    Warning: Cannot modify header information – headers already sent by (output started at /home/notiluca/www/wp-content/themes/twentyeleven/functions.php:775) in /home/notiluca/www/wp-includes/pluggable.php on line 876

  10. Thank you for that code development.
    It’s a perfect sample about how to do custom wordpress stuff and it’s well commented. Than you.

    About “use a media box” i’d try built-in WPAlchemy_metabox with great results.
    You can handle multiple upload boxes using field group open/close.
    An example is, that Realia template uses that to store multiple properties images to a single property :D


  11. Thanks for the tutorial. I started with the file upload button but feel I’m missing out on the media uploader functionality. Do you have a (perhaps extended?) tutorial available on how to tap into the media uploader (so the meta box works as the featured image meta box does)?

    • Unfortunately, I don’t have an extended tutorial.

      When you refer to the media upload functionality, do you mean just getting familiar with how it works and wanting to incorporate it into your theme?

      • Regarding the media uploader functionality, I was referring to how the user can select from the list of existing images already uploaded, or choose to upload a new image etc. The basic file upload button within a meta box is great and your tutorial helped me implement that perfectly, but now I’m thinking how do I let my admin-user reuse images that have already been uploaded? And I think replacing the basic file-upload button with a button that launches the media uploader will solve that problem.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.