WordPress is a great CMS and has a nice feature for uploading files. That is the media uploader which can be found inside the WordPress administration. In this tutorial we will not use the media uploader. We will develop our own WordPress file upload using AJAX and PHP.

The code used in this tutorial can be reused inside a theme or a plugin so feel free to experiment with it and create your own uploader. This tutorial will show you the code that can be used inside the WordPress administration or even on the front end.

There are different approaches on defining how to handle the file uploads:

  • using WordPress API – wp_handle_upload which will upload the file using the defined upload structure. Default structure is year/month inside the uploads folder.
  • using WordPress API – media_handle_upload which will upload the file and creating an attachment in the database so that the uploaded file can be seen inside the WordPress administration
  • using plain PHP to create a custom uploader

The upload form

Let’s assume that we have an upload form where the file will be inserted. This form will be hidden since we will create the upload via AJAX and PHP. To upload a file we will just click on an empty box that will be filled with an image or the filename. Here is our form:

The first element inside the form will be used to append upload messages such as a success message or even a failure message. Next one is the element #ibenic_file_upload and inside that element we will have the input which we will call upon clicking the #ibenic_file_upload.

The other element which is hidden is #ibenic_file_upload_preview and this element will be used to show the uploaded file. It will also have a button to delete the uploaded file.

Styling the form

We will add some simple styling to our form so that it can have a simple and slick look. You can change it on your own later but for now let’s just use this one. I will explain the trick we have used with our CSS.

First we need to enqueue our CSS. You could also just create a style tag before our form so you do add another HTTP request. I am showing you how to enqueue a style so that you can use that knowledge for your own plugin or theme.

The content of our CSS file is:

The two main containers with the class .file-upload were given a property position with the value relative. By declaring such property we can then position other elements inside that containers how we want. Our input is then positioned absolutely on the container with the widht and height set to 100%. In that way we have stretched our input element across the whole container. We did set the opacity to 0 because in that way the input is still clickable even thought we can’t see it.

With that trick we now have a clickable container which will open the pop-up to select a file for upload.

JavaScript and AJAX

The JavaScript written in this chapter can be placed below the form or even enqueued as a separate .js file if you are using this code or your theme or plugin. Here is an example on how to enqueue it inside a plugin:

Here we are setting the path to our file which in this example resides inside the folder js which is inside a folder assets. The file is also dependant of jQuery which we defined inside the array. The last boolean value defines if the script will be placed in the footer or in the header. We are placing it in the footer.

We are also using a WordPress function wp_localize_script that will set a global JavaScript object ibenicUploader with the property ajax_url which will hold the URL to the PHP file for handling AJAX calls.

First we are going to set some defaults before creating AJAX calls. Add this to your javascript file:

Here we are setting a click event on the element #ibenic_file_upload. This function that we are setting makes sure that the input will be clicked and that the window for choosing a file will open up. We are also setting an event trigger when the value of our input changes. This means that when we add a file to our input element, a function named prepareUpload will trigger. This function will prepare our data with the chosen file.

This function will also create an AJAX call on our site.

Here we are setting some defaults for the AJAX call and the success callback function that will be called when the AJAX is successfully completed. This means that the server returned a 200 response. If there was a 40x or 50x error status an error callback function will be called if that function was defined.

In the success callback function we will need to define everything related to showing the file preview and the delete button, but also hiding the current element that we are using for uploading the file. We will now create the rest of the code for the AJAX success even though we do not have any PHP functionality yet.

Here we are checking if the data object (JSON) has a property named response with a value of SUCCESS. If that is the response value then we will check if the data sent is of type image (jpeg, jpg, png or gif). If that is true then we will create an image element with the url to the image. If the file is not an image then we will just show its filename.

The created image or the filename will be appended to the element #ibenic_file_upload_preview. We target it by getting the ID of the first element which is used to upload the file: ibenic_file_upload. We then concatenate it with the string _preview so that we get the ID of the element for previewing the uploaded file.

We are also setting the uploaded filename on the delete button so that when we click on the delete we will know which file to delete. All that data that we are checking and setting (url, filename, response and type) are actually properties that will be set on the backend code with PHP.

The delete button and the delete functionality will be written later in this tutorial.

Reading the POST data

Now we will have to create a function that will be used to read the posted data and save the file on the server. Open your plugins’ or themes’ file where you will add that functionality and add this:

Here we are using the action wp_ajax_ibenic_file_upload which is set by our action sent in the AJAX call. This will work only for the logged in users. If you want to enable any visitor of your site to upload their files then you can do that by adding another action:

add_action("wp_ajax_nopriv_ibenic_file_upload", "ibenic_file_upload");

The only difference is the work nopriv which is added in the action name. Before we begin reading all the POST data we will set a variable with file errors:

Once we have set those errors we can start reading the data. For the first few lines we will read both the global $_POST variable and the $_FILES variable. Then we will merge them together so that we can access all the data under a single variable. Here is the code of that:

At the end of the function we have also added the function die(). Since we are creating a AJAX call on the admin_ajax.php file we do want to stop the reading process when we have already sent the required data as the response. Now with the variable $data we can read all the data that is sent by the AJAX call.

Using the WordPress API wp_handle_upload

Now that we have set our data in a variable, we can move on and start uploading our file. The first example is an example with the WordPress API wp_handle_upload. To read more about this function follow this link.

We are first defining an empty variable response to hold the response. After that we are using the wp_handle_upload function to upload the file. In this function we are sending the data of the file uploaded. Since we are appending that file data to the key ibenic_file_upload in the AJAX call, we are also referencing that data with the same key. The other data that is passed to this function is an array with some overrides. Since we are not sending this directly from a form but from an AJAX call we will not test the form.

After that we are checking if the file was uploaded and also if there was an error. If there was no error, we are sending a SUCCESS response with the data which we have already defined by setting the AJAX success callback function. If you do not remember them, they were:

  • Filename
  • URL to the file
  • File type

If there was an error, then we are just sending the error message with an ERROR response. At the end we are echoing that variable which is encoded in JSON. You can try to upload a file now and see if the image will show in the box or if the filename of another file will be rendered.

Using the WordPress API media_handle_upload

Let’s move on another WordPress upload function. This time, the function is the media_handle_upload. We will use the same function and we will not erase the functionality that we have added before. By doing that you can decide how you will upload your file and also with some other posted data we can decide which uploader to use because there can be different situation which will need a different way of uploading the file.

So at the beginning of our function we are defining a variable that will hold the number of the uploader we want to use. Below we have wrapped both uploader in IF statements. So since we are now using the number 2 uploader we are using the media_handle_upload. If you want to learn more about that function visit this link.

I will now explain only the wrapped code under the uploader number 2.

We are immediately calling the mentioned function where we are passing the string which is the key of our file upload. The second parameter is the post ID to which we want to attach this file. Since we have passed a zero (0) this function will not attach the file to any of the posts.

Once we have used that function we are checking if the variable is a WordPress error and if it is, then we are setting the ERROR response and we are getting the error message from our data variable where we have stored information about the uploaded file. If there is an error with the uploaded file, we will have a different number under the key error. We are then passing that number to the array fileErrors which holds all the error description for each error number.

If the attachment ID is not a WordPress error but a real ID we are getting the full path to the uploaded file. Then we are using a PHP function pathinfo to get some data about that file. From that data we can get the filename and the extension. If the extension is of an image type such as a jpg or png, we are extending the variable type with additional info to match the type in the AJAX success function. In that way, if the file is of extension jpgjpegpng or gif we will send image/jpgimage/jpegimage/png or image/gif and not only the extension.

We are also getting the URL of the uploaded file with the WordPress function wp_get_attachment_url.

Creating a custom WordPress file upload

We will not create our own custom WordPress file upload by uploading our files to the folder custom. This can be defined by you in any way you want. You could also added another layer and upload files to a folder named by the ID of a logged in user or something similar. For this example we will just upload the files to a folder custom.

I will now focus only on the wrapped code for the uploader number 3. Don’t forget to change the number of the variable ibenicUploader to number 3 so that this function can use that uploader.

So at the beginning of that wrapped code, we define several variables where we store information about the path to the upload folder and then using that information we are also creating two other paths which are the paths to our custom folder in the URI and URL form.

We are then checking if that folder exists and if it doesn’t we are creating it. After that we are getting the filename and then we are replacing all the empty places in underscores “_” so a file such as “Picture of me” will be “Picture_of_me”. We are now setting other information about the uploaded file such as the size, error and temporary name.

We are also creating a variable mb which will hold the maximum file size that is allowed to store. After that we are checking if there is an error inside the information about the uploaded file and if there is we are setting the ERROR response with the error message.

If there is no error, we are checking if there is already a file there with the same name and if there is one, we are again setting the ERROR response with the error message that such file already exists.

If there is no file error and the file does not exist already, we are checking for the size of the file. If the file is too big we will set the ERROR response with the error message that the file is too big. But if the file size is under the set size in the variable mb we can start saving the file in our custom folder.

If the file was successfully moved from the temporary folder to our custom folder, we are setting the SUCCESS response with all that required information. If the file was not successfully moved we will again set the ERROR response saying that the file upload failed.

Creating a fancy message for each upload or error

Open our CSS file and at the bottom of it add this few lines:

After that open our JavaScript file and add this function at the top of  $(document).ready(function(){});:

This will create a function that will append a new div element with the class .alert and append it to our previously defined element .ibenic_upload_message. After the message is shown, it will fade out slowly. To enable this message to show, we will edit our AJAX success callback function:

Here we have added two calls for the function add_message. One is when the response if SUCCESS and one is when the response is ERROR. We have also removed the alert for the error part since now we have a method to show the error message in a fancy way.

Deleting the Uploaded File

Here we will write the code that will delete the uploaded file and give us back the box to upload a file. First we are going to write some JavaScript code so open our JavaScript file and insert this before the end of $(document).ready(function(){});:

When the user click on the Delete button we will read the file url from the data attribute data-fileurl and add that data with the name of the action. Then we will create an AJAX call on it. In the SUCCESS callback function we will hide the preview box and show again the upload box. If the response is ERROR we will just show the error message given from the server.

We now only need to write the code in PHP to handle the deletion of the file, so open the file where we have already written the code for the upload and add this:

Here we are checking if the global $_POST variable is set. If it is set we are getting the information about the file url and then we are checking in the database if there is an attachment with that url. If that return true we are getting the ID of that attachment and then with the WordPress function wp_delete_attachment we are deleting the file.

If we did not get any information from the database then it must be a custom uploaded file. We are then getting the filename from the url and then we are creating the URI to the file. Here we are referencing the path to our custom folder. If you have named your folder differently then create the URI with your name. After that we can use the PHP function unlink and delete the uploaded file.

Creating a Secure Upload and Delete

For the upload and delete to be more secure we should use the WordPress Nonce. First let’s go to our form and add the nonce:

Now lets add that nonce to our AJAX calls. This will only show you where to add that code:

With that nonce added, the AJAX calls will also send the nonce data to our server. We now need to add a simple check for that nonce. Here you can see where to add them:

With this simple steps we have secured our upload and delete with WordPress Nonces.


WordPress file uploads can be done in many different ways. In this article we have covered only a few but there are also some other WordPress API functions that can upload files in WordPress.

Have you ever done something similar or developed a file uploader for WordPress? Share your experience in the comments below.

Become a Sponsor

Posted by Igor Benic

Web Developer who mainly uses WordPress for projects. Working on various project through Codeable & Toptal. Author of several ebooks at https://leanpub.com/u/igorbenic.


  1. Awesome tutorial – I do it within the next days. Thank you in advance.


    1. Hi Toby, I hope you had great success trying it! 🙂


  2. Hello Igor,

    Thanks for posting such a wonderful explanation for uploader.
    Can you please advise me on my requirement which is exactly opposite of what you have explained here.
    I want to send image as a json data from wp server to an ajax request that I have received from the browser.

    Thanks and Regards


    1. Hi Sanjiv, I am glad you like the information provided in this tutorial. To send an image as JSON, you would need to have a logic to get the image data. If the image is stored in media library you can get the information about a particular image by using functions like: https://codex.wordpress.org/Function_Reference/wp_get_attachment_metadata. You would need an ID provided by the AJAX request to get the requested image.


  3. Hay, why can’t delete file, try use media_handle_upload., and how to allow multiple file upload.


    1. Hi Rahman, thank you for reading the article and for the questions. I’ll try to answer them for you. If you are going to delete a file that has been saved also as an attachment, you can use wp_delete_attachment. If you want have a file stored somewhere else, in a custom folder, without saving the same as an attachment in WordPress you can use the function wp_delete_file where you provide the full path to the file.

      To upload multiple files, you would use the $_FILES global variable and then use the key name where you will have all those file names. You could use something like foreach( $_FILES[NAME_OF_YOUR_INPUT]['name'] as $filename ) {} . Remember that other parts of the $_FILES works in the same manner. To check the type of your second file you will need to use $_FILES['type'][1] since the first file is at index 0.

      I hope I have answered all your questions:)


      1. I’m confused, can you give we all sample code for multiple file upload?


  4. i got “undefined” error. when i am trying to upload file


    1. Hi Richa, when do you get that undefined error? Is it from PHP or JavaScript (Console)?


      1. i got undefined error in alert and when i console data I found FormData is null .


  5. Hello Igor,

    if I use the script and select a file for the upload, I get the error message “undefined”. This message comes from the js-function. Starting from the place >> if (data.response == “SUCCESS”) {<< I get an undefined.

    I use the actually version of wordpress.

    Could you please help me? I would like to use your script, because it can help me with an urgent problem.

    Many Thanks


    1. Hi Oliver,

      I have tried the code and it works fine for me. Are you sure that the AJAX is done correctly? It seems that it might be that you don’t get anything returned by the server. That also can happen if you’re not logged in and you are using only the wp_ajax_ action and not wp_ajax_nopriv_.


      1. Hello Igor,

        Thank you for your reply. Could you please give me the code as files?

        Maybe I just made an error while working through your article. wp_ajax_nopriv_ I have used …

        thank you in advance


        1. If you have used the wp_ajax_nopriv_ then you must be logged in for that action to trigger.

          You can get the files here: https://www.ibenic.com/wp-content/uploads/2017/10/ajax-test.zip

          This zip is a plugin folder so you can upload it to your WordPress install and place [test_ajax] to get the upload form. Use that only on localhost;)


          1. Hello Igor,

            Thanks for the files. Your plugin runs perfectly.

            My problem seems to be somewhat more specific. You use a shortcode that works. I need to integrate the script or the form via hook. Currently I use ‘loop_start’ or ‘loop_end’. For both it does not work and I get an error message (“undefined”). Can you include the script of you at all by Hook?

            Sorry for the circumstances, but you help me with your support in the matter.

            Many Thanks

          2. Hello Igor,

            first of all thank you very much for your help. Your code is greatful…

            I could find the error. I haved in the plugin before the function an if query, with which I had checked whether the URL is true. If I take this away, so the function ibenic_file_upload exists normal in the plugin file, then they works. The error or the cause does not quite open up to me. Do you have an explanation for why WordPress responds?

            Thank you in advance

          3. Hi Oliver, sorry I did not quite get it what you are trying to say. If you mean that you had an IF statement before the definition of the function ibenic_file_upload, then it might be that the IF statement was false and the rest of the plugin file wasn’t processed?

          4. Hi Igor,

            thank you for your feedback.

            i have used an if-query before ein declared the function.
            if ($val == true) { // if
            function ibenic_file_upload () { …. }
            } // end if
            Thats doesn’t work.

            That only works, when i declared the function without if-query.
            That is it what i not understand.

          5. That might not work for AJAX because the value of the variable might not be filled. You could maybe use wp_doing_ajax() to check if it’s an AJAX request but since the AJAX request is loaded after every plugin, your if statement might be ignored again.

          6. Hello Igor,

            thank you for the explanation, which explains a lot to me.

            A final question on the matter I have: Can I use parts of your code for my projects?

            thank you in advance

          7. thank you so much

  6. what if the user goes into the form and edit the file name, so he can delete anything from server?
    and what if the user uploads some php file to run malicious stuff on server?
    How would you handle those problems to make it more secure, and how would you limit the file types allowed to send?


    1. Hi Lucian, if the user changes the name used for the file input field, the user won’t be able to upload a file. Each uploaded file should be saved under a secure file permission such as 644 so that even if that file is a PHP one, it can’t be executed.

      To limit the file types, you can have an array of valid extensions and check if the current file’s extension is in that array. If it’s not, you are stopping the process of upload. If you’re using WordPress functions such as wp_handle_upload, you can pass a parameter with allowed file types. You can see how to do it here: https://wordpress.stackexchange.com/questions/37230/how-to-set-file-type-in-wp-handle-upload.


  7. This for some reason does not work on my wordpress after I updated to the latest version: Version 4.9.8.

    Any ideas?


    1. No idea, maybe a function has changed. I’ll have to chek it out also.


  8. Awesome work!! Thank you still working to day I tested method 1 and 3.


  9. For people who want to implement multiple upload here is the configuration.


    $.each(file, function(key, value)
    data.append(“ibenic_file_upload[]”, value);

    PHP(edit to include this):
    $list_files = $data[“ibenic_file_upload”];
    $file_count = count($list_files[‘name’]);

    // Loop Multiple files
    for ($i=0; $i<$file_count; $i++) {
    $fileName = $list_files["name"][$i];
    $fileNameChanged = str_replace(" ", "_", $fileName);
    $temp_name = $list_files["tmp_name"][$i];
    $file_size = $list_files["size"][$i];
    $fileError = $list_files["error"][$i];
    } // End for files
    My implementation was for method number (3)


    1. Amit MaharXan July 1, 2019 at 4:36 am

      hi Kash,
      how can we achieve multiple uploads ?? it’s not working.. details plz..


  10. Hello, thank you for this rich tutorial !
    I can see the upload button only when adding opacity and there’s no submit button, how does this work?
    Feel so newb…
    Would be lovely to get some help on this … ^^
    Have a great day 🙂


    1. Hi Olivier, the button is hidden on purpose since the JavaScript will listen to click events on the DIV element containing the button. CSS should be style that so that the element is wide enough to be clickable.


  11. Amit MaharXan July 1, 2019 at 4:49 am

    hi Igor Benic,
    thanks for the awesome tutorial.. i tried method 2 and it worked.. but i need to implement multiple images upload with it.. how to achieve this..


    1. Hi Amit, to upload multiple files, you would need to make a form that accepts multiple files. For example, the input field can be something like this:

      Then, you can check the PHP manual on how to access the information on each file: https://www.php.net/manual/en/features.file-upload.multiple.php


  12. I think the line
    wp_enqueue_style( ‘ibenic-style’, plugins_url( ‘assets/css/style.css, dirname( __FILE__ ) )’ );

    needs to be changed to

    wp_enqueue_style( ‘ibenic-style’, plugins_url( ‘assets/css/style.css’, dirname( __FILE__ ) ) );

    Thank you for your support to the community.


  13. I know this is quite an old post but the trick still works and i was able to upload and preview the image but however i am facing two problems.

    1. How do i attach the uploaded image to the post in the form because i am creating a frontend post publishing form.

    2. How do i set the image as the featured image of the post which i am publishing.

    I have been searching for a solution but none worked for me.

    Help needed please. Thanks.


    1. Hi John,

      so to attach the uploaded image to the post, it depends where you want to attach it. If you want to add it to the post’s content, you can get the post

      $post = get_post(ID_OF_THE_POST);
      $content = $post->post_content;
      $content .= YOUR_IMAGE_HTML_HERE (you can even check how gutenberg produces the block on the front or even in the backend)
      wp_update_post( array( 'ID' => $post->ID, 'post_content' => $content ));

      As for the featured image, you need to use the code example to add the image to the media so it can be seen as an attachment, get the ID of the attachment and then update_post_meta( $post_id, '_thumbnail_id', $attachment_id);


  14. Hi, I am currently working on a my first wordpress site and I am trying to allow visitors on the site to leave a post. Just a simple form, where they type in their name, email, a comment and then upload a media file.

    I currently have it set up to where a visitor can upload a post (that is pending on the admin dashboard). Though I am having a hard time understanding how I am going to upload media with these posts. Or how I will form the relation between the post and the media?

    From what I understand, all media will be uploaded to the WP media directories. I just don’t know how to relate the visitor posts with the media? What I currently have is a post page that will loop through all the posts and display them. In that loop, I want to also grab any media associated with that post so I can style it along with the post. Can anyone help me understand how to do this? Or maybe shine some light on this topic.

    Thank you.


  15. I am successfully uploading the images to the WordPress media. Although whenever this code executes it shows it being an error? Even when there is no error. I print the error in ajax, and it says the status is 200 and everything worked. So why does this PHP think there is a WP_Error?

    // Upload the file using media_handle_upload
    $attachment_id = media_handle_upload( ‘file_upload’, 0 );

    if ( is_wp_error( $attachment_id ) ) {
    // send back the correct error
    $response[‘response’] = “ERROR”;
    $response[‘error’] = $fileErrors[ $data[‘file_upload’][‘error’] ];
    } else {


    1. Do you know what is the error there? I’ll have to rewrite this whole tutorial with some newer ideas and code 🙂


      1. Same exact error but mine dosent work


  16. Here is the set up to upload multiple files using upload method two “media_handle_upload”:



    1. Figured I would leave this here. I spent too long trying to figure this out. SO hopefully this will help someone else in the future.


      // append all of the files data
      $.each(f, function(key, value)
      data.append(“file_upload[]”, value);

      function form_upload() {
      $fileErrors = array(
      0 => “There is no error, the file uploaded with success”,
      1 => “The uploaded file exceeds the upload_max_files in server settings”,
      2 => “The uploaded file exceeds the MAX FILE SIZE from HTML form”,
      3 => “The uploaded file uploaded only partially”,
      4 => “No file was uploaded”,
      6 => “Missing a temporary folder”,
      7 => “Failed to write file to disk”,
      8 => “A PHP extension stoped file to upload” );

      // This will represent all of our AJAX data.
      $posted_data = isset( $_POST ) ? $_POST : array();
      $file_data = isset( $_FILES ) ? $_FILES : array();
      $data = array_merge( $posted_data, $file_data );

      // handle inserting the new post to the backend
      $post_data = array(
      ‘post_title’ => $data[‘name’],
      ‘post_content’ => $data[‘text’],
      ‘post_status’ => ‘pending’, // Automatically publish the post.
      ‘post_author’ => NULL,
      ‘post_category’ => array( 1, 3 ), // Add it to two categories.
      ‘post_type’ => ‘post’ // defaults to “post”. Can be set to CPTs.

      // Lets insert the post now.
      $post_ID = wp_insert_post( $post_data );
      $response[‘post_ID’] = $post_ID;

      // Upload the file using media_handle_upload
      $url = [];
      $pathinfo = [];
      $error = [];
      $count = 0;
      foreach ($data[‘file_upload’][‘name’] as $key => $value) {
      if ($data[‘file_upload’][‘name’][$key]) {
      $file = array(
      ‘name’ => $data[‘file_upload’][‘name’][$key],
      ‘type’ => $data[‘file_upload’][‘type’][$key],
      ‘tmp_name’ => $data[‘file_upload’][‘tmp_name’][$key],
      ‘error’ => $data[‘file_upload’][‘error’][$key],
      ‘size’ => $data[‘file_upload’][‘size’][$key]

      $_FILES = array(“upload_file” => $file);
      $attachment_id = media_handle_upload(“upload_file”, $post_ID);

      if (is_wp_error($attachment_id)) {
      // There was an error uploading the image.
      $response[‘response’] = “ERROR”;
      $error[$key] = $file[‘error’];
      } else {
      // The image was uploaded successfully!
      $response[‘response’] = “SUCCESS”;
      $error[$key] = $file[‘error’];
      $fullsize_path = get_attached_file( $attachment_id );
      $pathinfo[$key] = pathinfo( $fullsize_path );
      $url[$key] = wp_get_attachment_url( $attachment_id );

      $response[‘error’] = $error;
      $response[‘url’] = $url;

      echo json_encode($response);



Leave a reply

Your email address will not be published. Required fields are marked *

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