I'm trying to upload files from a multiple file upload HTML input via ajax on a WordPress site but I keep getting a 400 error and I'm not sure why. My code is based on this tutorial.



  var fd = new FormData();
  var files_data = $(this); // The <input type="file" /> field

  // Loop through each data and create an array file[] containing our files data.
  $.each($(files_data), function(i, obj) {
          fd.append('files[' + j + ']', file);

  fd.append('action', 'file_upload');

  fd.append('nonce', $('#file_upload_nonce').val());

  fd.append('application_id', $(this).closest('.application_question').attr('data-application-id'));

      type: 'POST',
      url: '/wp-admin/admin-ajax.php',
      data: fd,
      contentType: false,
      processData: false,
      success: function(response){



function file_upload(){
  // Check the nonce first
  if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'file_upload' ) ) {
    echo 'Security validation failed.';
  } else {

    $application_id = $_POST['application_id'];

    foreach ( $_FILES['files']['name'] as $f => $name ) {

      move_uploaded_file( $_FILES["files"]["tmp_name"][$f], '/wp-content/supporting-evidence/' . $application_id . '/' . $_FILES["files"]["name"][$f] );



What am I doing wrong here?

  • Where did you put your PHP code, is it in the theme functions file? Were you logged-in when you tried your form?
    – Sally CJ
    Feb 7, 2023 at 13:15
  • The PHP is within a plugin file and I was logged in when I tested the form (it's designed to only be accessed by logged in users). EDIT: You've just helped me realise that I wasn't @includeing the PHP file (doh!) I'm not getting the 400 error now, but the files aren't being uploaded.
    – AJT
    Feb 7, 2023 at 13:28
  • Have you checked, via the browser console, whether the AJAX request is being sent properly? If yes, try adding a echo 'it works'; after the foreach loop in your PHP function, then see if the AJAX response includes that it works text.
    – Sally CJ
    Feb 7, 2023 at 14:22
  • I can see in the comments you are no longer getting the 400 error - did you find a solution? Can you check if you var_dump($_FILES) in your php file if there are any files being sent over ? Have you included enctype="multipart/form-data" as an attribute on the form which you are submitting? I can see you are using the application id to organise your folders within supporting evidence - do you need to create the directory before uploading the file to that location using mkdir() function i.e. if(!is_dir('/wp-content/supporting-evidence/'.$application_id)) { mkdir('/wp-content/supporting-ev Feb 7, 2023 at 16:49
  • @AJT can you leave that you didn't include the file as an answer? It's not obvious that you found the solution, the site doesn't know this and people don't always leave comments
    – Tom J Nowell
    Feb 8, 2023 at 14:00

1 Answer 1


It's because you're logged out and the code does not handle that

There is also a major security hole in the code, scroll down to see the details

HTTP 400 and a 0 is what admin-ajax.php returns when it cannot find a handler for your AJAX request.

Note that if you had used the modern REST API instead of the legacy admin-ajax.php API it would have told you this in plaintext human readable language and a 404 like this:

{"code":"rest_no_route","message":"No route was found matching the URL and request method.","data":{"status":404}}

It would also have responded with a 403 forbidden if you'd made the same mistake, which is much harder to make when using AJAX with a REST API endpoint

If we look at the tutorial we can see where it adds the AJAX handler action:

add_action('wp_ajax_cvf_upload_files', 'cvf_upload_files');
add_action('wp_ajax_nopriv_cvf_upload_files', 'cvf_upload_files'); // Allow front-end submission

But, if we look at the code in your question:


Your code has 1 action, the tutorial has 2. You're missing the wp_ajax_nopriv_ version.

Overrall, keep in mind this is an 8 year old tutorial from 2015, and you shouldn't follow just a single tutorial when researching things. The official WordPress developer docs explain this much better, and since that tutorial was written a new modern API for AJAX called the REST API was written.

Further Notes

  • you never checked that move_uploaded_file worked or not, it returns a false value if it failed, you need to check for this or it will fail silently and you'll be scratching your head trying to figure out why files are missing
  • it needs to create the folder!
  • you should not assume the location of the upload folder, the tutorial you shared uses wp_upload_dir() instead of assuming /wp-content/uploads to do this
  • What if $_POST['application_id'] contained something malicious? e.g. ../? or myapplicationID/and/this/is/a/subfolder?
  • the AJAX happens every time the file input changes, which means if I accidentally pick the wrong file, it's instantly uploaded and I can't get it back! What if I picked something confidential by accident that was next to it and now we're both in trouble? Or if I select the same file 5 times, now there are 5 copies on your server. What if I accidentally select a 24GB blueray backup file?
    • the tutorial also has a check for this, but your code does not

🚨 A Major Security Problem In The Code 🚨

  • There is no validation on the files, I could use this to upload and run PHP files!!!!
    • the tutorial you're following uses the wp_check_filetype function to do this
    • I could use your form to submit PHP files that give me total control over your website then just visit example.com/wp-content/supporting-evidence/myapplicationID/hacksite.php, this is very dangerous!
    • at a minimum check the file extensions, WordPress has functions for checking valid filetypes
  • Actually, he was logged-in.. it's just his AJAX action wasn't registered, because he forgot to include the PHP file containing his add_action() code. He also said that the form was meant for logged-in users only.. But as for the rest in your answer, e.g. using the REST API, or a WordPress-specific function for handling the uploads, I totally agree :)
    – Sally CJ
    Feb 8, 2023 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.