Since the release of version 3.3, WordPress is using a brand new media uploading engine – Plupload. Plupload is an awesome uploader created by the developers of TinyMce. Depending on the device, it can use HTML5, Gears, Silverlight, Flash, BrowserPlus, or normal forms to provide features such as upload progress, auto image resizing, and asynchronous uploads.
I am sure you have used “Upload Media” button at the top of wysiwyg editor in wordpress admin (see the image on the right hand side). WordPress core team used Plupload to make that media uploader. And, in this tutorial, we will find out how we, theme and plugin developers, can use it to handle image uploads in our admin screens. In this article, we will create a custom image uploader that uploads behind the scene (ajax), allows multiple images upload in one go, displays upload progress to the user, handles image resizing automatically, and works in metaboxes too. Yes, it’s one heck of an uploader.
Let’s start by writing down a list of features our uploader must have.
- It allows both multiple and single image upload.
- It uploads images asynchronously.
- It displays upload progress.
- It works in metaboxes.
- We must be able to place more than one uploader in a page.
- It handles image resizing automatically.
- It displays thumbnails of images user has uploaded.
- And optionally, it allows user to drag the thumbnails to change order of the images.
Long list? I know. But don’t worry, most of the needed functionality is provided by Pluploaded out of the box.
Before we start, here are the demos of final uploader.
Demo 1 shows pluploader in theme/plugin admin page.
Demo 2 shows pluploader in a metabox
Step 1: Include necessary javascripts and css styles.
Code
<?php function plu_admin_enqueue() { if(!($condition_to_check_your_page))// adjust this if-condition according to your theme/plugin return; wp_enqueue_script('plupload-all'); wp_register_script('myplupload', '/adjust-this-url/myplupload.js', array('jquery')); wp_enqueue_script('myplupload'); wp_register_style('myplupload', '/adjust-this-url/myplupload.css'); wp_enqueue_style('myplupload'); } add_action( 'admin_enqueue_scripts', 'plu_admin_enqueue' ); ?> |
Explanation
We use the “admin_enqueue_scripts” hook to include our scripts and styles. Please don’t include these files where you don’t need them. You can use an if-condition to test if this is the page you want to add this uploader to. We add Plupload scripts bundled with wordpress core. We also add the script and style we used to make this uploader: myplupload.js and myplupload.css
Step 2: Use the admin_head hook to print some script.
Code
<?php function plupload_admin_head() { // place js config array for plupload $plupload_init = array( 'runtimes' => 'html5,silverlight,flash,html4', 'browse_button' => 'plupload-browse-button', // will be adjusted per uploader 'container' => 'plupload-upload-ui', // will be adjusted per uploader 'drop_element' => 'drag-drop-area', // will be adjusted per uploader 'file_data_name' => 'async-upload', // will be adjusted per uploader 'multiple_queues' => true, 'max_file_size' => wp_max_upload_size() . 'b', 'url' => admin_url('admin-ajax.php'), 'flash_swf_url' => includes_url('js/plupload/plupload.flash.swf'), 'silverlight_xap_url' => includes_url('js/plupload/plupload.silverlight.xap'), 'filters' => array(array('title' => __('Allowed Files'), 'extensions' => '*')), 'multipart' => true, 'urlstream_upload' => true, 'multi_selection' => false, // will be added per uploader // additional post data to send to our ajax hook 'multipart_params' => array( '_ajax_nonce' => "", // will be added per uploader 'action' => 'plupload_action', // the ajax action name 'imgid' => 0 // will be added per uploader ) ); ?> <script type="text/javascript"> var base_plupload_config=<?php echo json_encode($plupload_init); ?>; </script> <?php } add_action("admin_head", "plupload_admin_head"); ?> |
Explanation
The only reason we are not placing this script in an external file is that we need php and wordpress environment to output some data. In short, this is a base configuration array which will be used to configure uploaders.
Step 3: Placing html in your forms
Code
<?php // adjust values here $id = "img1"; // this will be the name of form field. Image url(s) will be submitted in $_POST using this key. So if $id == “img1” then $_POST[“img1”] will have all the image urls $svalue = ""; // this will be initial value of the above form field. Image urls. $multiple = true; // allow multiple files upload $width = null; // If you want to automatically resize all uploaded images then provide width here (in pixels) $height = null; // If you want to automatically resize all uploaded images then provide height here (in pixels) ?> <label>Upload Images</label> <input type="hidden" name="<?php echo $id; ?>" id="<?php echo $id; ?>" value="<?php echo $svalue; ?>" /> <div class="plupload-upload-uic hide-if-no-js <?php if ($multiple): ?>plupload-upload-uic-multiple<?php endif; ?>" id="<?php echo $id; ?>plupload-upload-ui"> <input id="<?php echo $id; ?>plupload-browse-button" type="button" value="<?php esc_attr_e('Select Files'); ?>" class="button" /> <span class="ajaxnonceplu" id="ajaxnonceplu<?php echo wp_create_nonce($id . 'pluploadan'); ?>"></span> <?php if ($width && $height): ?> <span class="plupload-resize"></span><span class="plupload-width" id="plupload-width<?php echo $width; ?>"></span> <span class="plupload-height" id="plupload-height<?php echo $height; ?>"></span> <?php endif; ?> <div class="filelist"></div> </div> <div class="plupload-thumbs <?php if ($multiple): ?>plupload-thumbs-multiple<?php endif; ?>" id="<?php echo $id; ?>plupload-thumbs"> </div> <div class="clear"></div> |
Explanation
We begin this code by adjusting some values for this specific uploader unit. We can request multiple file uploads and image resizing using those values.
We are using a hidden field to store all the image urls. This hidden field will be submitted in $_POST. Additionally, you must provide existing saved value (image urls) in $svalue variable. We added information like ajax nonce, multiple file upload, resize width and height in html tags (in classes, and in empty span tags – a bit unorthodox, but it will work perfectly. You can use data attributes here if you want to.)
Step 4: Setup ajax handler to handle asynchronous file upload.
Code
<?php function g_plupload_action() { // check ajax noonce $imgid = $_POST["imgid"]; check_ajax_referer($imgid . 'pluploadan'); // handle file upload $status = wp_handle_upload($_FILES[$imgid . 'async-upload'], array('test_form' => true, 'action' => 'plupload_action')); // send the uploaded file url in response echo $status['url']; exit; } add_action('wp_ajax_plupload_action', "g_plupload_action"); ?> |
Explanation
We check the ajax nonce, handle file upload, and send the url of uploaded file as response.
Myplupload.js
Code
jQuery.fn.exists = function () { return jQuery(this).length > 0; } jQuery(document).ready(function($) { if($(".plupload-upload-uic").exists()) { var pconfig=false; $(".plupload-upload-uic").each(function() { var $this=$(this); var id1=$this.attr("id"); var imgId=id1.replace("plupload-upload-ui", ""); plu_show_thumbs(imgId); pconfig=JSON.parse(JSON.stringify(base_plupload_config)); pconfig["browse_button"] = imgId + pconfig["browse_button"]; pconfig["container"] = imgId + pconfig["container"]; pconfig["drop_element"] = imgId + pconfig["drop_element"]; pconfig["file_data_name"] = imgId + pconfig["file_data_name"]; pconfig["multipart_params"]["imgid"] = imgId; pconfig["multipart_params"]["_ajax_nonce"] = $this.find(".ajaxnonceplu").attr("id").replace("ajaxnonceplu", ""); if($this.hasClass("plupload-upload-uic-multiple")) { pconfig["multi_selection"]=true; } if($this.find(".plupload-resize").exists()) { var w=parseInt($this.find(".plupload-width").attr("id").replace("plupload-width", "")); var h=parseInt($this.find(".plupload-height").attr("id").replace("plupload-height", "")); pconfig["resize"]={ width : w, height : h, quality : 90 }; } var uploader = new plupload.Uploader(pconfig); uploader.bind('Init', function(up){ }); uploader.init(); // a file was added in the queue uploader.bind('FilesAdded', function(up, files){ $.each(files, function(i, file) { $this.find('.filelist').append( '<div class="file" id="' + file.id + '"><b>' + file.name + '</b> (<span>' + plupload.formatSize(0) + '</span>/' + plupload.formatSize(file.size) + ') ' + '<div class="fileprogress"></div></div>'); }); up.refresh(); up.start(); }); uploader.bind('UploadProgress', function(up, file) { $('#' + file.id + " .fileprogress").width(file.percent + "%"); $('#' + file.id + " span").html(plupload.formatSize(parseInt(file.size * file.percent / 100))); }); // a file was uploaded uploader.bind('FileUploaded', function(up, file, response) { $('#' + file.id).fadeOut(); response=response["response"] // add url to the hidden field if($this.hasClass("plupload-upload-uic-multiple")) { // multiple var v1=$.trim($("#" + imgId).val()); if(v1) { v1 = v1 + "," + response; } else { v1 = response; } $("#" + imgId).val(v1); } else { // single $("#" + imgId).val(response + ""); } // show thumbs plu_show_thumbs(imgId); }); }); } }); function plu_show_thumbs(imgId) { var $=jQuery; var thumbsC=$("#" + imgId + "plupload-thumbs"); thumbsC.html(""); // get urls var imagesS=$("#"+imgId).val(); var images=imagesS.split(","); for(var i=0; i<images.length; i++) { if(images[i]) { var thumb=$('<div class="thumb" id="thumb' + imgId + i + '"><img src="' + images[i] + '" alt="" /><div class="thumbi"><a id="thumbremovelink' + imgId + i + '" href="#">Remove</a></div> <div class="clear"></div></div>'); thumbsC.append(thumb); thumb.find("a").click(function() { var ki=$(this).attr("id").replace("thumbremovelink" + imgId , ""); ki=parseInt(ki); var kimages=[]; imagesS=$("#"+imgId).val(); images=imagesS.split(","); for(var j=0; j<images.length; j++) { if(j != ki) { kimages[kimages.length] = images[j]; } } $("#"+imgId).val(kimages.join()); plu_show_thumbs(imgId); return false; }); } } if(images.length > 1) { thumbsC.sortable({ update: function(event, ui) { var kimages=[]; thumbsC.find("img").each(function() { kimages[kimages.length]=$(this).attr("src"); $("#"+imgId).val(kimages.join()); plu_show_thumbs(imgId); }); } }); thumbsC.disableSelection(); } } |
Explanation
For each uploader unit, we create a configuration array using the base configuration array, and pass it to the plupload.Uploader constructor. We then attach the following events:
1. Init:
This gets fired on the initiation of uploader object. This currently doesn’t do anything in our code.
2. FilesAdded:
This gets fired when user adds files using “Select Files” button. We use this event to show file unit containing filename and filesize. This unit has a small progress bar at the bottom. We also automatically start uploading selected files asynchronously.
3. UploadProgress:
This gets fired when we receive upload progress. We use it to display upload progress (both textual and graphic).
4. FileUploaded:
This gets fired when we receive upload complete notification from the server. We use it to fadeOut the file unit. In Step 4, we sent the url of uploaded file as response. We receive that url in this event. Depending on whether if we are using multiple files upload, we store this url in the hidden field. We then refresh the image thumbs below the select button area.
5. Image thumbs at the bottom:
We display thumbnails of the images which user has provided below the uploader. We also place a link to remove image below images. We use jQuery sortable ui to allow user to change order of the images by dragging thumbnails.
Myplupload.css
Code
.filelist { width: 60%; } .filelist .file { padding: 5px; background: #ececec; border: solid 1px #ccc; margin-bottom: 4px; } .filelist .fileprogress { width: 0%; background: #B7C53D; height: 5px; } .plupload-thumbs { } .plupload-thumbs .thumb { width: 50px; padding-right: 5px; float: left; } .plupload-thumbs .thumb img { width: 50px; height: 50px; } .ui-sortable .thumb img { cursor: pointer; } |
Explanation
Css to style the uploader.
Handling Form Upload:
You will receive image urls in $_POST[“img1”] (see Step 3 to adjust “img1”) in csv format. For example: imgurl1,imgurl2,imgurl3
Conclusion:
In this article we have seen what an awesome addition Plupload is to the wordpress core. We can use it in our themes and plugins. We can even use it in metaboxes, which is awesome since normal file uploads don’t work in metaboxes. Now that you know how to use Plupload, you have the knowledge to create custom media uploaders for wordpress.


Thank you for another fantastic post. The place else may anyone get that kind of info in such a perfect method of writing?
I have a presentation next week, and I am at the search for such
info.
It works perfect,
I only added ‘jquery-ui-sortable’ to enable drag and drop.
Thanks a lot!!
wp_register_script(‘myplupload’, ‘/adjust-this-url/myplupload.js’, array(‘jquery’, ‘jquery-ui-sortable’));
I should have added to my previous comment, that I can upload the sames files via the WordPress Media “Add New” without any problem.
So, I’m thinking that it’s something that it’s something I have configured incorrectly when trying to implement your code.
Hey Sam,
Since you are receiving 0 from the ajax request, we can conclude that the ajax request being sent to wordpress is not being received by our code (see step 4 in this tutorial).
Check these things:
1) In step 2, we wrote: ‘action’ => ‘plupload_action’, // the ajax action name
In step 4, we wrote: add_action(‘wp_ajax_plupload_action’, “g_plupload_action”);
The action names must match. (ie ‘plupload_action’ must match plupload_action part in wp_ajax_plupload_action)
2) The add_action(‘wp_ajax_plupload_action’, “g_plupload_action”); code in step 4 must be placed where it can be approached on ajax request.
Ajax request doesn’t run hooks like “admin_enqueue_scripts” and “admin_head” etc. So keep that line outside of any such hooks.
Do let me know how it goes.
Many thanks for the fast response to my question.
I have it working now.
Your response made me think about about the placement of
add_action(‘wp_ajax_plupload_action’, “g_plupload_action”);
and I realised that I had placed it where it was not getting triggered.
Once I repositioned that in my code everything started working fine.
Again, many thanks for the great tutorial – you’ve helped me a lot.
Many thanks for the great tutorial.
I implemented it in a metabox, and everything looks like it is working but the uploads are failing.
I can select the files, and I see the files being uploaded and the percentages increase to 100%, but then the IDs return zero, and the files have not been uploaded.
Is there anything you could suggest that I check to try and find the reason for the file upload error?
Thanks for any help you can provide.
Thanks so much Krishna!
This is going to work great in a plugin that I am updating.
Nick Powers
You sir is my hero. Thanks for this and continue the excellent work..
Krishna,
This is an amazing article – well written, great illustration and movie, very helpful; please write articles more often, the WordPress community needs them!
Will be in touch!
Cheers, Brian
Hi Krishna,
Thanks for a great article! I’ve modified your script so that the uploaded images will be added as attachements. Now I’m trying to remove the images from being attached when clicking the Remove-link. Just so that I understand it right: does the image being removed in the call:
thumb.find("a").click(function()Or where does the action take place?
Kind regards /NIklas
Hi,
Yes, you are right. Image is being removed in that call.
thumb.find("a").click(function() {
var ki=$(this).attr("id").replace("thumbremovelink" + imgId , "");
ki=parseInt(ki);
var kimages=[];
imagesS=$("#"+imgId).val();
images=imagesS.split(",");
for(var j=0; j
kimages[kimages.length] = images[j];
}
}
$("#"+imgId).val(kimages.join());
plu_show_thumbs(imgId);
return false;
});
Basically, image urls are stored in csv format in a hidden field [$("#"+imgId)]. In that code we are just removing the url of that image from the hidden field's csv. This doesn't delete the actual physical file on your server. If you want to delete those files (or remove it from attachments) then you can add another hidden field containing list of urls (or attachment id), when user submits the form you can read values from this hidden field and delete/remove the files/attachments.
-Krishna
Good work Krishna Kant Sharma. I really like this one trick
Hi, great job! I’m a newbie to WP, so please allow me one stupid question. How can I allow users to upload files from the WP page? I mean I want to create page template with upload functionality and then create several pages for different kinds of data. I know this must be simple, but I’m stuck.
Thanks in advance!
Thanks for the tutorial, but i can’t get it to do anything. I guess I’m stuck at where I would place the html section. Does that go in a metabox?
gr8 going man. it was a great help!
krishna, this looks brilliant. i am going to try to implement it into my metabox. had a question before i get too involved, b/c at first glance I couldn’t tell: is it possible for the user to select a file from the media library if it exists?
Hi Kathy,
I am glad you like it.
No, it currently doesn’t allow user to select a file from the media library.
ok, so i finally got around to trying to put this in a metabox. got it saving so that’s a good start. also i removed your plupload_admin_head()function in favor of creating that object with wp_localize_script(). Now: to try to get the true ‘drag and drop’ magic of plupload.
I couldn’t follow the step3. I am suppose to add it to a metabox. I have created a metabox and assigned step3 as the class to the metabox.
But nothing seems to happen. Any ideas please?
Hi Bage,
If you want to use it in Metabox then you will need to place it in your function which renders metabox.
For more information please see http://codex.wordpress.org/Function_Reference/add_meta_box
Hey, great tutorial. The only thing is that sometimes my user’s won’t be admins but they will be allowed to upload images and I’d love to use this. I know I can update the AJAX to use the nopriv one but would I need to do something different in Step #2?
Hi Ryan,
Thanks, I am glad you like it.
To use it in frontend you will have to modify step1 and 2 too. You will need to use frontend hooks in place of admin hooks (“admin_enqueue_scripts” and “admin_head”).
How do you save the data? I have this meta box working and it’s AWESOME, but when I update the post, the data is not saved.
Also, how would I retrieve the data?
Thanks!
Hey, don’t worry about my last comment. I found an alternative.
Thanks,
Jason
Ok. Great.
But just for the reference: http://codex.wordpress.org/Function_Reference/add_meta_box
That page explains how you attach action to “save_post” and use that to save the data.
What others are saying about this article: