Practical WordPress Development

Create a WordPress Upload Meta Box

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.


  1. shawn

    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

    • Tom McFarlin

      Thanks for the comment – tried to keep it as simple and as functional as possible :).

      It’s possible to do that and I’ve got experience with it, but it’ll take longer than a comment to cover on how to do it. I’ll make a note and plan to do something like this in a future article. Can’t promise it’ll be soon, but I’ll be sure to share something about it.

      • shawn

        Thanks Tom
        that’s all a guy can ever ask for :)

        Once the new pupload came out, I’ve seen little to no interest in doing it ‘old school’. I appreciate your time answering back.

    • John

      For adding the files through wordpress meta box. See full tutorial at:

      • Tom McFarlin

        Thanks for sharing an additional resource on this.

        There were a couple of things I noticed about the code and the methodology you’re using to upload the file. If you’re interested in talking more about it, let me know!

  2. Jenny Beaumont

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

    • Tom McFarlin

      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. Jenny Beaumont

    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!

    • Tom McFarlin

      This shouldn’t happen – the plugin is written in such a way that if an image isn’t supplied, it simply skips the logic. This is the first issue I’ve heard about this.

      Shoot me a link to a page on your blog and I’ll take a look.

      • Jenny Beaumont

        Hey – thanks for getting back to me so quickly. Not sure what you could see from the frontend as it’s a backend issue. Ultimately everything works great, just the error message when creating a new post and no file is uploaded. I could send you my modified code maybe?

        • Tom McFarlin

          Oh! My fault – I misread the post on which this comment was posted (was commenting in the backend :)).

          So this must be the result of something that’s changed since I first wrote the code. Unfortunately, I don’t have time to go back and revise this right now.

          If this is something you really need some help with, feel free to contact me and let me know!

          • Julien LE THUAUT

            Hello Tom, Jenny,
            Same issue for me unfortunately :(
            I thought it was maybe due to the javascript test but I can’t find the real problem… This feature would help me since I’m trying to create a CPT on which you can add multiple files.
            If someone find something, that would be great to share it ;)

          • Tom McFarlin

            I’ll make a note to look into this as soon as I can. Perhaps tomorrow, but it may be next week.

            I’ll post here as soon as I have a solution in place.

    • Tom McFarlin

      Hey guys,

      This has been fixed. I’ve written a plugin full of example code that’s available here for both review, and for download.

      It’s been tested against WordPress 3.5.1 – and should be fully compliant.

      I’ll be updating the post as well.

      Hope this helps!

  4. Jenny Beaumont

    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!!

    • Tom McFarlin

      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 :/.

      • Jenny Beaumont

        Hey Tom,

        Thanks for the speedy reply – will give the update a try *fingers crossed*. Otherwise, I saw the 2 part tutorial you did on tutsplus – any different than this code? (sorry, I could compare, but thought asking would be quicker :)).


        • Tom McFarlin

          This code is much more updated, better organized, and implements a few best practices that I wasn’t using at the time when I wrote the Tuts+ article.

  5. Jenny Beaumont

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

  6. Colin

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

    • Tom McFarlin

      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?

      • Colin

        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!

        • Tom McFarlin

          Once the plugin is active, you’d use this:

          get_post_meta( $post_id, 'umb_file' );

          To get the path to the file :).

          • Colin

            Hey Tom,

            Sorry it took me so long to say thanks. As it were I’m also having the same problem as the user above – not having any submit button or option to save once the file has been specified in the upload path. Any help?


            • Tom McFarlin

              There isn’t a submit button or anything – everything happens when you click ‘Publish’ or ‘Update’ post.

              Hope this helps :)!

  7. Lois

    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

    • Tom McFarlin

      Hey Lois,

      What kind of file did you attempt to upload? This could be related to a number of different things. Make sure you’re also running the latest version from here.

  8. John

    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

    • Tom

      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.

      • John

        Hi Tom, I turned Debugging on in the wordpress config file but it didn’t return any errors at all, not even in the log?

        • Tom

          The next thing I’d try is doing a line-by-line `print_r` or `var_dump` of what’s going on to see where the uploading is filing.

          I wish I could be more help. but I’m backed up with other stuff right now.

  9. Tamiris

    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

    • Tom

      Looks like you’re missing something in your functions.php file (specifically on line 775). Perhaps you’re doing an echo, print_r, or var_dump, or something similar.

  10. erm3nda

    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. Henry

    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)?

    • Tom

      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?

      • Henry

        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.

        • Tom

          Unfortunately, I don’t have a tutorial for that – the best I’m able to offer, at least right now, is to study how WordPress core does it and then leverage that to your advantage.

          • Henry

            Thanks for the idea, I’ll give that a shot.

Leave a Reply

© 2020 Tom McFarlin

Theme by Anders NorenUp ↑