Try Tuts+ Premium, Get Cash Back!
Attaching Files To Your Posts Using WordPress Custom Meta Boxes, Part 1

Attaching Files To Your Posts Using WordPress Custom Meta Boxes, Part 1

Tutorial Details
  • Program: WordPress
  • Version (if applicable): 3.3
  • Difficulty: Intermediate
  • Estimated Completion Time: 1 hour
This entry is part 6 of 6 in the series WordPress Dashboard Customizations

Over the course of the next two posts, we’ll take a look at how we can leverage the WordPress API to define our own custom meta boxes for attaching a document (such as a PDF) to our WordPress pages. We’ll also take a look at how to properly remove files should we choose to delete them from our posts.

A few months ago, Christopher Davis demonstrated how to create Custom WordPress Write/Meta Boxes (http://wp.tutsplus.com/tutorials/plugins/how-to-create-custom-wordpress-writemeta-boxes/). As mentioned in his article, custom meta boxes are incredibly powerful features that allow us to add various pieces of additional information to our WordPress posts and pages.

Of course, we’re not limited to text or options such as radio buttons or checkboxes. We can also attach images and files to posts, pages, and other post types by taking advantage of custom meta boxes; however, dealing with file types requires a bit more code to properly handle uploads and deletions of the file.

Before getting started, it’s important to note that this series assumes that we’re working with the Twentyeleven Theme. Although the code will work with any WordPress Theme, this ensures that we’re all working with the same codebase and should make it easier to follow along with the tutorial.


Defining The Custom Meta Box

First, let’s create the post meta box. Specifically, the meta box should…

  • Be available on both posts and pages
  • Appear beside the post editor
  • Accept an input file

At this point, locate functions.php in the root of the Twentyeleven Theme directory. We’ll be making all of our changes to the bottom of the file. We’ll start by defining a function called add_custom_meta_boxes and it will be registered with the add_meta_boxes hook.

function add_custom_meta_boxes() {

} // end add_custom_meta_boxes
add_action('add_meta_boxes', 'add_custom_meta_boxes');

Next, let’s define our custom meta box. First, we’ll write the code after which I’ll explain what the code is doing:

function add_custom_meta_boxes() {

	// Define the custom attachment for posts
	add_meta_box(
		'wp_custom_attachment',
		'Custom Attachment',
		'wp_custom_attachment',
		'post',
		'side'
	);
	
	// Define the custom attachment for pages
	add_meta_box(
		'wp_custom_attachment',
		'Custom Attachment',
		'wp_custom_attachment',
		'page',
		'side'
	);

} // end add_custom_meta_boxes
add_action('add_meta_boxes', 'add_custom_meta_boxes');

Notice that the following two calls to add_meta_box are nearly identical.

  • The first parameter is the ID of the meta box. This is used when saving the value.
  • The second parameter is the label. This value appears in the caption above the post meta box
  • The third value is the callback function that is used to actually define the markup that appears in the meta box. We’ll get to this momentarily.
  • The fourth value tells WordPress the post type on which this custom meta box should appear. Since we want it on both posts and pages, we’ve defined it twice.
  • The final parameter defines where we want the meta box to appear. It can be either side, advanced, or normal. We’ve select side so that it appears beside the post editor

Setting Up The Callback

At this point, the custom meta box doesn’t do anything. In fact, it doesn’t even display anything:

This is because we haven’t defined the callback function that is used generate the markup for the meta box. In order to do that, we need to define the function with the name that we listed it above. Specifically: ‘wp_custom_attachment.’

The input should accept a PDF so we’ll give a short description and the proper input element for accepting files:

function wp_custom_attachment() {

	wp_nonce_field(plugin_basename(__FILE__), 'wp_custom_attachment_nonce');
	
	$html = '

'; $html .= 'Upload your PDF here.'; $html .= '

'; $html .= ''; echo $html; } // end wp_custom_attachment

The first line of the code defines a nonce value in order to properly validate and secure our upload.

Next, we’re simply setting up the markup for displaying the input field.

At this point, we’ve got a custom meta box that looks decent but doesn’t actually work.


Saving The File

Now we’re ready to save the file. First, we need to create a function that hooks into the save_post hook. Let’s define that now:

function save_custom_meta_data($id) {

	/* --- security verification --- */
	if(!wp_verify_nonce($_POST['wp_custom_attachment_nonce'], plugin_basename(__FILE__))) {
	  return $id;
	} // end if
	  
	if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
	  return $id;
	} // end if
	  
	if('page' == $_POST['post_type']) {
	  if(!current_user_can('edit_page', $id)) {
	    return $id;
	  } // end if
	} else {
   		if(!current_user_can('edit_page', $id)) {
	    	return $id;
	   	} // end if
	} // end if
	/* - end security verification - */
	
} // end save_custom_meta_data
add_action('save_post', 'save_custom_meta_data');

Although this function doesn’t actually save the value – not yet, at least – it includes a bit of code that ensures we’re ready to save the file. Specifically, the function makes sure that the expected nonce value is present, that an automatic save is not occurring, and that the user attempting to save data has permissions to do so.

Now we’re ready to begin validating and saving the file. When it comes to saving file-based custom meta data, additional code has to be introduced to properly handle indiosynchrocies of uploading files. First, we’ll define the code then we’ll explain it. The function should look like this:

function save_custom_meta_data($id) {

	/* --- security verification --- */
	if(!wp_verify_nonce($_POST['wp_custom_attachment_nonce'], plugin_basename(__FILE__))) {
	  return $id;
	} // end if
	  
	if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
	  return $id;
	} // end if
	  
	if('page' == $_POST['post_type']) {
	  if(!current_user_can('edit_page', $id)) {
	    return $id;
	  } // end if
	} else {
   		if(!current_user_can('edit_page', $id)) {
	    	return $id;
	   	} // end if
	} // end if
	/* - end security verification - */
	
	// Make sure the file array isn't empty
	if(!empty($_FILES['wp_custom_attachment']['name'])) {
		
		// Setup the array of supported file types. In this case, it's just PDF.
		$supported_types = array('application/pdf');
		
		// Get the file type of the upload
		$arr_file_type = wp_check_filetype(basename($_FILES['wp_custom_attachment']['name']));
		$uploaded_type = $arr_file_type['type'];
		
		// Check if the type is supported. If not, throw an error.
		if(in_array($uploaded_type, $supported_types)) {

			// Use the WordPress API to upload the file
			$upload = wp_upload_bits($_FILES['wp_custom_attachment']['name'], null, file_get_contents($_FILES['wp_custom_attachment']['tmp_name']));
	
			if(isset($upload['error']) && $upload['error'] != 0) {
				wp_die('There was an error uploading your file. The error is: ' . $upload['error']);
			} else {
				add_post_meta($id, 'wp_custom_attachment', $upload);
				update_post_meta($id, 'wp_custom_attachment', $upload);		
			} // end if/else

		} else {
			wp_die("The file type that you've uploaded is not a PDF.");
		} // end if/else
		
	} // end if
	
} // end save_custom_meta_data
add_action('save_post', 'save_custom_meta_data');

The new block of code does several things. Comments have been provided in order to give clarity, but here’s what’s happening:

  • First, we make sure the file array isn’t empty
  • Next, we setup an array for the supported file types and verify that the uploaded file is of that type
  • Next, we use wp_upload_bits to copy the file to the server
  • Finally, if there are any errors, we’ll halt execution and display them to the user.

A note on wp_upload_bits(http://codex.wordpress.org/Function_Reference/wp_upload_bits). This function is an alternative to wp_handle_upload(http://codex.wordpress.org/Function_Reference/wp_handle_upload). In my experience, wp_handle_upload has yielded some problems with certain server configurations such that it gives a false negative. By that, I mean that it claims that it has a problem uploading the file when, in reality, the file was actually uploaded.


Linking Up the File

At this point, we should be ready to provide a link to the file on our posts and our pages. In the Twentyeleven Theme directory, locate the two files single.php and page.php. Each file contains a line of code that looks like this:

get_template_part( 'content', 'single' );

Just below that line, we’ll need to request the custom post meta information by doing this:

echo get_post_meta(get_the_ID(), 'wp_custom_attachment', true);

Specifically, this function is requesting the post meta data identified by ‘wp_custom_attachment’ associated with this post ID. Clear, right? The last parameter is telling WordPress that we want the result back in a string format (the alternative is in the format of an array and that’s beyond the scope of this tutorial).

The final block of code should look like this:

get_template_part( 'content', 'single' );
echo get_post_meta(get_the_ID(), 'wp_custom_attachment', true);

Now, attempt to upload a file for a page or post. Load up the post and page in your browser. Permitting everything has gone correctly, you’ll notice that … oops … you don’t actually have the path to a file.


Update The Post Form

The problem is that, by default, the form element used to save all of the post information and its associated data doesn’t accept file types. This can be fixed with adding one more function that hooks into the WordPress page life cycle:

function update_edit_form() {
    echo ' enctype="multipart/form-data"';
} // end update_edit_form
add_action('post_edit_form_tag', 'update_edit_form');

This will append the enctype attribute to the post editor form element so that file uploads will not be supported.

Now, let’s try to upload a file a again. Locate your post or page with the custom post meta box and attempt to upload a PDF. If all goes well, you should be able to navigate to the post and/or page and see the URL to the file.


Linking It Up

The last step is the easiest. Next up, revisit the single.php and page.php files and wrap the call to the custom meta data request in an anchor such that it looks like this:




	Download PDF Here

At this point, you should be able to attach a custom PDF to your page and have a link appear at the bottom of the page content providing a link to the download. In the next post, we’ll take a look at how we can provide some better styling for the download anchor as well as delete the file from the page using the WordPress API.

In the meantime, try experimenting with customizing the meta box even further. For example, attempt to include a custom label to provide content for the link.


Other parts in this series:Three Practical Uses for Custom Meta Boxes
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://tentblogger.com John (TentBlogger)

    killer series. the man knows his stuff!

    • http://tommcfarlin.com Tom McFarlin
      Author

      Thanks John!

  • http://www.herewithme.fr Amaury

    Better and easier : Use WPalchemy and MediaAccess class…
    http://www.farinspace.com/wordpress-media-uploader-integration/

    • http://tommcfarlin.com Tom McFarlin
      Author

      Cool stuff – thanks for sharing this.

  • http://www.newcarmodels.co.uk/ Hadie Danker

    Thanks for the tut, I am going to try some of these

    • http://tommcfarlin.com Tom McFarlin
      Author

      Awesome – good luck with it!

  • pouya

    it’s awesome! thanks dude.

    • http://tommcfarlin.com Tom McFarlin
      Author

      Sure thing!

  • Valerie

    It’d be nice if you could download the code…it’s scattered in bits and pieces through the tutorial and I could learn more if I could see it all put together.

    Interesting, though.

    • http://tommcfarlin.com Tom McFarlin
      Author

      Will keep that in mind for the next tutorial!

  • http://wpthemepower.com Zeeshan

    Already tried this! Makes WordPress so much easier and simpler. Great functionality to add when giving out freebies and other tutorial downloads as PDF.
    http://wpthemepower.com/category/tutorials/

  • http://www.soulsizzle.com Ryan Marganti

    Seeing at least a couple errors in your code. Firstly, whether or not if(‘page’ == $_POST['post_type']) is true, you are still executing the same code. Secondly, in line 24 of save_custom_meta_data(), the word empty appears twice. Just a heads up.

    • http://tommcfarlin.com Tom McFarlin
      Author

      Nice catches – thanks Ryan.

      Yeah – not sure what’s up with the duplicate ‘empty’ call. Think the code editor botched it after publishing. Either way, hopefully it’s still clear ;).

  • Brendo

    Often I’m creating portfolio pages, with custom fields for each item. However, recently I had to create a portfolio with fields linked between portfolio items.

    Example:

    Portfolio Thumb 1 shares a video link with Portfolio Thumb 2 – so they should be linked somehow in a box with the shared link.

    Have no idea how to do this – any ideas? Anyone else have to link custom posts in a custom way?

  • http://horttcore.de Ralf Hortt
  • http://kovshenin.com Konstantin

    Great tutorial Tom, well done! I would work a little bit more on coding standards though, it would really help people get around the code faster and understand those arrays in a function in a function in a function much faster. Keep them coming!

    ~ K

  • http://www.basinhosting.com James M

    Thanks for the tutorial! I wonder if there is a way to upload the file to a special directory? A directory just for these PDF uploads and be able to choose the directory, instead of wp-content/uploads/ .

  • Pingback: Attaching Files To Your Posts Using WordPress Custom Meta Boxes, Part 2 | Wptuts+

  • Pingback: My Stream » Attaching Files To Your Posts Using WordPress Custom Meta Boxes, Part 2

  • http://8mediaeg.com 8MEDIA

    thanks very much for this amazing lessons 6 on 6 .. i want to ask if i could make a custom directory for each file .. and also .. if i want it`s like open with a short link protect ? and it will be amazing if it`s appear only for registers users !!

  • http://ironman.artlogo.ro Aurel

    great tut.
    one small correction:

    instead of

    // Make sure the file array isn’t empty
    if(!emptyempty($_FILES['wp_custom_attachment']['name'])) {

    use this

    // Make sure the file array isn’t empty
    if(!empty($_FILES['wp_custom_attachment']['name'])) {

    • http://wakeusup.com Kamal Khan

      This has already been pointed out and corrected in the previous comments. Make sure you read the comments :)

  • http://scottcarlton.net Scott

    This is exactly what I have been looking for. I can’t seem to get it to work though in WP 3.2.1. I copy and paste up to Update the Post From but nothing happens. I get “Array” printed on the post page. I took out the extra empty. Is there a complete file to download or look at? Has anyone else had this issue?

    • Von Eric

      i also encountered this issue, it doesnt work in WP 3.2.1 :(

  • RitaSiminu

    :)

  • Manu

    Nice tutorial, I needed something like this. Any idea how to upload directly to something like amazon s3?

  • Josh

    Comprehensive tutorial, but it doesn’t work the way it’s supposed to – at least not in 3.3.1. Also there are several errors in the code itself. It would be far easier to follow if the entire code base was provided at the end of the tutorial.

    Following the tutorial exactly I get php errors. When I fix the end paragraph tag I get an upload field that uploads files but doesn’t put them anywhere. The callback doesn’t work on page reload and the end output creates an anchor tag with no href reference. It may very well be because I’ve missed a step, something that could be remedied by posting the entire code rather than piecemal segments.

    Great effort, but I can’t use it because I can’t get it to work.

    • Ryan

      I am having the same exact issues. What Do?

  • http://www.findajobfair.com Rowela Alzona

    this is awesome…

  • ANNA

    I have a issue, when I submit, file dulicate, ex sub.pdf, sub1.psf … please tell me why ?

  • ANNA

    I have a issue, when upload file, file duplicate, ex: file.pdf file1.pdf. please tell me how to fix !!!

  • http://www.pachakamaq.com Fliberty

    Very good tutorial, thank you very much.

  • Pingback: Attaching Files with Custom Post Meta Boxes - Tom McFarlin

  • http://www.jsxtech.com Jaspal Singh

    Excellent tutorial. thanks for sharing.

  • http://housser.ca John

    Great tutorial, have just been using it as I get up to speed on WordPress. I’m seeing the same issue as Anna, with it saving two copies of the files. I’ve found that save_custom_meta_data is getting called twice during the postback, but not sure why yet.

  • MG

    Does the link to the uploaded file supposed to display within the ‘edit post’ view once a file has been uploaded?

  • Tavo

    This was awesome! Huge help!

  • Help

    For those of you having trouble with an outputted array. In the code in the last “linking it up” section of the tutorial, the value of the href doesn’t look the way it should. This is actually php that looks like this

    You have to access the url parameter of the within array. As wordpress has store more than one value when you upload the image.
    $img['url']

  • http://www.diascodes.com dIascoDes

    That was awesome , Thanks Tom

  • http://jacksonholewebdesigners.com Matthew Lee

    Thank you Tom. Great Post. Most helpful. I noticed one thing. I don’t know if this is new or not but get_post_meta(get_the_ID(), ‘wp_custom_attachment’, true); does not return a string. It returns an array with the file name and the file url for the pdf. Thus to get the file name and the link, you need to use the below or something similar. I scratched my head at this one for a bit so hopefully it will help others.

    Thanks!

    $pdf_info = get_post_meta(get_the_ID(), ‘wp_custom_attachment’, true);
    $pdf_file = $pdf_info['file'];
    $pdf_link = $pdf_info['url'];

    • http://twitter.com/RoeeYossef Roee Yossef

      Hi Matt,

      i get the same issue as you, can you explain a bit further on how to implement your solution ??

      Thanks !

  • http://developers.litefort.com Litefort Dev

    we posted a step-by-step tutorial to creating a custom upload component for wordpress (including JQuery and TinyMCE integration) for those interested in a different approach (replacing the WP upload mechanism).
    http://developers.litefort.com/blog/2012/09/20/wordpress-custom-file-upload-component-step-by-step-tutorial/

  • http://www.facebook.com/motov Deian Motov

    This is a working solution! Congratulations on this!

  • BiG ViK

    Great tut….

    been stuck with this problem a whole day. Solved it after reading this post.
    Great work !!!!

  • http://twitter.com/potasiyam Tanbin Islam Siyam

    Will this work for images too? What if I need multiple types of files?

  • http://www.facebook.com/circuit.circus Circuit Circus

    For me the stated hook for the first function (Defining The Custom Meta Box) doesn’t return anything. Had it fixed as I recognized this and decided to hook into admin_init! Better

  • http://twitter.com/ArcherGod Sumit Gupta

    Hello, Tutorial is great but I too have problem with Duplicate file upload. Not sure why still searching for it.

  • http://www.facebook.com/profile.php?id=100005217789905 Lucas Gabriel

    good article and it works great. but What if I don’t just wanto to have to UPLOAD a single file, but SEVERAL, and have an option to NAME the link I wanna display in the front-end?

  • http://www.facebook.com/rafael.dacosta.7587 Rafael Da Costa

    I added the following to display the file in the admin meta box if it exists . is this the best approach? note: my file type is ‘image/jpeg’

    $coverImg = get_post_meta(get_the_ID(), ‘wp_custom_attachment’, true);

    if($coverImg['url']){echo ”;}

  • http://twitter.com/RoeeYossef Roee Yossef

    Amazing ! works great .. Thanks !

  • Marius

    I’ve made a debug : print_r($_FILES), but this variable is empty on save post.

  • Swamy

    Nice tutorial.. But here no download option. If you provide that too it is more usefull..

    Anyhow it’s works great..and thanks for sharing..