470

I'm using jQuery and Ajax for my forms to submit data and files but I'm not sure how to send both data and files in one form?

I currently do almost the same with both methods but the way in which the data is gathered into an array is different, the data uses .serialize(); but the files use = new FormData($(this)[0]);

Is it possible to combine both methods to be able to upload files and data in one form through Ajax?

Data jQuery, Ajax and html

$("form#data").submit(function(){

    var formData = $(this).serialize();

    $.ajax({
        url: window.location.pathname,
        type: 'POST',
        data: formData,
        async: false,
        success: function (data) {
            alert(data)
        },
        cache: false,
        contentType: false,
        processData: false
    });

    return false;
});

<form id="data" method="post">
    <input type="text" name="first" value="Bob" />
    <input type="text" name="middle" value="James" />
    <input type="text" name="last" value="Smith" />
    <button>Submit</button>
</form>

Files jQuery, Ajax and html

$("form#files").submit(function(){

    var formData = new FormData($(this)[0]);

    $.ajax({
        url: window.location.pathname,
        type: 'POST',
        data: formData,
        async: false,
        success: function (data) {
            alert(data)
        },
        cache: false,
        contentType: false,
        processData: false
    });

    return false;
});

<form id="files" method="post" enctype="multipart/form-data">
    <input name="image" type="file" />
    <button>Submit</button>
</form>

How can I combine the above so that I can send data and files in one form via Ajax?

My aim is to be able to send all of this form in one post with Ajax, is it possible?

<form id="datafiles" method="post" enctype="multipart/form-data">
    <input type="text" name="first" value="Bob" />
    <input type="text" name="middle" value="James" />
    <input type="text" name="last" value="Smith" />
    <input name="image" type="file" />
    <button>Submit</button>
</form>
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Dan
  • 11,914
  • 14
  • 49
  • 112
  • 2
    The `FormData` approach should work fine with forms that contain whatever you want, not just the file upload fields; it is not widely supported though. – lanzz Jun 05 '12 at 14:48
  • @lanzz which though? the one with serialize seems to work only for data but the other seems only to work for files? – Dan Jun 05 '12 at 14:52
  • Judging by [this MDN page](https://developer.mozilla.org/en/XMLHttpRequest/FormData), all form data should be submitted when you use `FormData` – lanzz Jun 05 '12 at 14:53
  • 1
    @lanzz you are right, it works how I thought it should be I was using the wrong form id, you can upload both files and data via one form with ajax. – Dan Jun 05 '12 at 15:00
  • This seem not to work when there is multi-select file input. It only uploads the first file. – Sami Al-Subhi Apr 23 '18 at 16:04

13 Answers13

534

The problem I had was using the wrong jQuery identifier.

You can upload data and files with one form using ajax.

PHP + HTML

<?php

print_r($_POST);
print_r($_FILES);
?>

<form id="data" method="post" enctype="multipart/form-data">
    <input type="text" name="first" value="Bob" />
    <input type="text" name="middle" value="James" />
    <input type="text" name="last" value="Smith" />
    <input name="image" type="file" />
    <button>Submit</button>
</form>

jQuery + Ajax

$("form#data").submit(function(e) {
    e.preventDefault();    
    var formData = new FormData(this);

    $.ajax({
        url: window.location.pathname,
        type: 'POST',
        data: formData,
        success: function (data) {
            alert(data)
        },
        cache: false,
        contentType: false,
        processData: false
    });
});

Short Version

$("form#data").submit(function(e) {
    e.preventDefault();
    var formData = new FormData(this);    

    $.post($(this).attr("action"), formData, function(data) {
        alert(data);
    });
});
Dan
  • 11,914
  • 14
  • 49
  • 112
  • i tried the above method, it is working good, but i need to display output as "file uploaded successfully Thanks for uploading".. But i am getting
    tag in the response,how to overcome this problem
    – lalith222 Apr 30 '13 at 07:13
  • Don't use Internet Explorer? ... in all seriousness I don't know, I'm pretty sure it works for me, try and debug the problem a little more, what doesn't work? and why do you have x2 accounts? – Dan Jul 31 '13 at 10:53
  • 17
    in versions of IE < 10 this solution will not work, as FormData is an HTML5 object, not present in IE 8 or 9. – Xavier Guzman Sep 18 '13 at 17:14
  • how to get the file without `form` ? – Oki Erie Rinaldi Oct 22 '14 at 03:35
  • 36
    `$(this)[0]` is just an alias of `this`, so `new FormData(this)` should be sufficient. – r3wt Dec 12 '14 at 07:05
  • 9
    It doesn't seem to be possible to inspect the FormData Object, see [this question](http://stackoverflow.com/questions/17066875/how-to-inspect-formdata) (for anyone running into the same cluelessness as I just did because the Object was always empty). – Laura Dec 12 '14 at 14:02
  • 31
    For future readers: the contentType and processData declarations are important. See [this answer](http://stackoverflow.com/a/12756023/16911) for more information. – AaronSieb Jul 06 '15 at 19:49
  • I tried the shorter method in your answer. I does not seem to working for me. However the longer version with async : false works. ------- Im using file:// and can.fixture() from canJS to emulate post end point. – pravin Aug 23 '15 at 21:15
  • 5
    the `async: false` does not seem required for this to work and causes blocking on mobile (single threaded) browsers – Jeremy Daalder Oct 21 '16 at 00:05
  • Do u have any idea how backend developer gonna fetch form file and form data from this request. – Gaurav Aggarwal Jan 30 '17 at 12:31
  • 1
    cache option seems not necessary. Short version does not work for me. – AGuyCalledGerald Aug 02 '17 at 10:24
  • Is there any fallback for IE<10 – Anshuman Aug 26 '18 at 03:34
  • In my case I had to use: contentType : "application/octet-stream" – Christophe Roussy Sep 19 '18 at 13:51
  • @ChristopheRoussy — That will break things. You need `contentType: false`. – Quentin Apr 30 '19 at 13:14
  • @inullpointer — Not really. The only way to work with them is to use the hidden iframe hack. … but it has been years since those browsers have received any security update and their market share is tiny. – Quentin Apr 30 '19 at 13:16
  • @GauravAggarwal — The same as any normal multipart form submission. The specifics depend on the programming language and form handling libraries. – Quentin Apr 30 '19 at 13:16
  • @Quentin I indeed ended up using `contentType: false` with `name` attribute on the file input also used on the server-side. – Christophe Roussy May 06 '19 at 10:30
  • I'm using this solution but the form seems to only submit the files and not the value in ``, is there any suggestion what to look at? – Darren Christopher Oct 18 '19 at 01:27
  • Note that the **shorter version** may not give an [Illegal Invocation](https://stackoverflow.com/q/12755945/1526703) and you may need to set `contentType` and `processData` as in the longer version – Anupam Mar 31 '21 at 12:21
34

another option is to use an iframe and set the form's target to it.

you may try this (it uses jQuery):

function ajax_form($form, on_complete)
{
    var iframe;

    if (!$form.attr('target'))
    {
        //create a unique iframe for the form
        iframe = $("<iframe></iframe>").attr('name', 'ajax_form_' + Math.floor(Math.random() * 999999)).hide().appendTo($('body'));
        $form.attr('target', iframe.attr('name'));
    }

    if (on_complete)
    {
        iframe = iframe || $('iframe[name="' + $form.attr('target') + '"]');
        iframe.load(function ()
        {
            //get the server response
            var response = iframe.contents().find('body').text();
            on_complete(response);
        });
    }
}

it works well with all browsers, you don't need to serialize or prepare the data. one down side is that you can't monitor the progress.

also, at least for chrome, the request will not appear in the "xhr" tab of the developer tools but under "doc"

Roey
  • 1,647
  • 21
  • 16
  • 1
    Indeed it is not Ajax, still can be useful to people with the same question. – Roey Jul 24 '16 at 06:00
  • 3
    I just can't believe why this answer get -2, I ended up using this as I needed legacy browser Support – Sijav Jul 28 '16 at 18:36
  • This answer should be in the thread as other answers notes 'older browsers dont work' or 'iframe hack could be used' but never addresses them. Nice piece of code, also showing how to use onload correctly +1 – mschr Jul 23 '19 at 08:04
28

I was having this same issue in ASP.Net MVC with HttpPostedFilebase and instead of using form on Submit I needed to use button on click where I needed to do some stuff and then if all OK the submit form so here is how I got it working

$(".submitbtn").on("click", function(e) {

    var form = $("#Form");

    // you can't pass Jquery form it has to be javascript form object
    var formData = new FormData(form[0]);

    //if you only need to upload files then 
    //Grab the File upload control and append each file manually to FormData
    //var files = form.find("#fileupload")[0].files;

    //$.each(files, function() {
    //  var file = $(this);
    //  formData.append(file[0].name, file[0]);
    //});

    if ($(form).valid()) {
        $.ajax({
            type: "POST",
            url: $(form).prop("action"),
            //dataType: 'json', //not sure but works for me without this
            data: formData,
            contentType: false, //this is requireded please see answers above
            processData: false, //this is requireded please see answers above
            //cache: false, //not sure but works for me without this
            error   : ErrorHandler,
            success : successHandler
        });
    }
});

this will than correctly populate your MVC model, please make sure in your Model, The Property for HttpPostedFileBase[] has the same name as the Name of the input control in html i.e.

<input id="fileupload" type="file" name="UploadedFiles" multiple>

public class MyViewModel
{
    public HttpPostedFileBase[] UploadedFiles { get; set; }
}
h_power11
  • 333
  • 3
  • 11
22

Or shorter:

$("form#data").submit(function() {
    var formData = new FormData(this);
    $.post($(this).attr("action"), formData, function() {
        // success    
    });
    return false;
});
Mike Doe
  • 16,349
  • 11
  • 65
  • 88
schaenk
  • 257
  • 1
  • 2
  • 4
  • so with this, how do you validate a data field using the same script, that is if you have a text field and a file field in your form – George Sep 02 '14 at 17:03
19

EDIT: with the new version of JQuery (3.6), you could also try using contentType function argument instead of enctype. Try contentType: multipart/form-data.


For me, it didn't work without enctype: 'multipart/form-data' field in the Ajax request. I hope it helps someone who is stuck in a similar problem.

Even though the enctype was already set in the form attribute, for some reason, the Ajax request didn't automatically identify the enctype without explicit declaration (jQuery 3.3.1).

// Tested, this works for me (jQuery 3.3.1)

fileUploadForm.submit(function (e) {   
    e.preventDefault();
    $.ajax({
            type: 'POST',
            url: $(this).attr('action'),
            enctype: 'multipart/form-data',
            data: new FormData(this),
            processData: false,
            contentType: false,
            success: function (data) {
                console.log('Thank God it worked!');
            }
        }
    );
});

// enctype field was set in the form but Ajax request didn't set it by default.

<form action="process/file-upload" enctype="multipart/form-data" method="post" >
     
     <input type="file" name="input-file" accept="text/plain" required> 
     ...
</form>

As others mentioned above, please also pay special attention to the contentType and processData fields.

Adithya Upadhya
  • 2,239
  • 20
  • 28
  • 1
    "For me, it didn't work without enctype: 'multipart/form-data' field in the Ajax request." — That can't have had any effect. It isn't a property recognised by jQuery.ajax. See [the documentation](http://api.jquery.com/jquery.ajax/) where `enctype` isn't mentioned at all. – Quentin Apr 30 '19 at 13:14
  • Like I mentioned before, I tried multiple different answers but they failed to work. An Ajax error stating encoding error was shown in the JS console. Later I followed [this tutorial](https://www.mkyong.com/jquery/jquery-ajax-submit-a-multipart-form/) which finally made my code work and I posted it here. Perhaps, `enctype` field is not covered in the documentation for a reason. I haven't checked jQuery source code so I cannot say with certainty. – Adithya Upadhya May 01 '19 at 14:29
  • "Perhaps, enctype field is not covered in the documentation for a reason." — That reason is that jQuery doesn't do anything with it so it is nonsense. – Quentin May 01 '19 at 14:30
  • Perhaps you could [contact Mkyong](https://www.mkyong.com/contact-mkyong/) and have a conversation with him instead. I tested my code again by removing the `enctype` field and it no longer uploads files (encoding type error returns). I'm not sure how that works since I haven't checked the jQuery source code. I posted this answer with the intention of helping others who are stuck in a similar problem. I'm not fishing for upvotes here... If you have further questions/comments, let's chat instead of commenting. – Adithya Upadhya May 01 '19 at 14:37
9

A Simple but more effective way:
new FormData() is itself like a container (or a bag). You can put everything attr or file in itself. The only thing you'll need to append the attribute, file, fileName eg:

let formData = new FormData()
formData.append('input', input.files[0], input.files[0].name)

and just pass it in AJAX request. Eg:

    let formData = new FormData()
    var d = $('#fileid')[0].files[0]

    formData.append('fileid', d);
    formData.append('inputname', value);

    $.ajax({
        url: '/yourroute',
        method: 'POST',
        contentType: false,
        processData: false,
        data: formData,
        success: function(res){
            console.log('successfully')
        },
        error: function(){
            console.log('error')
        }
    })

You can append n number of files or data with FormData.

and if you're making AJAX Request from Script.js file to Route file in Node.js beware of using
req.body to access data (ie text)
req.files to access file (ie image, video etc)

kartik tyagi
  • 6,256
  • 2
  • 14
  • 31
3

The code below works for me

$(function () {
    debugger;
    document.getElementById("FormId").addEventListener("submit", function (e) {
        debugger;
        if (ValidDateFrom()) { // Check Validation 
            var form = e.target;
            if (form.getAttribute("enctype") === "multipart/form-data") {
                debugger;
                if (form.dataset.ajax) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    var xhr = new XMLHttpRequest();
                    xhr.open(form.method, form.action);
                    xhr.onreadystatechange = function (result) {
                        debugger;
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            debugger;
                            var responseData = JSON.parse(xhr.responseText);
                            SuccessMethod(responseData); // Redirect to your Success method 
                        }
                    };
                    xhr.send(new FormData(form));
                }
            }
        }
    }, true);
});

In your Action Post Method, pass parameter as HttpPostedFileBase UploadFile and make sure your file input has same as mentioned in your parameter of the Action Method. It should work with AJAX Begin form as well.

Remember over here that your AJAX BEGIN Form will not work over here since you make your post call defined in the code mentioned above and you can reference your method in the code as per the Requirement

I know I am answering late but this is what worked for me

Vivaan
  • 141
  • 2
  • 14
2

Just to remind, in 2022 you don't need to use jquery. Try js standard Fetch API

    var formData = new FormData(this);

    fetch(url, { 
      method: 'POST',
      body: formData
    })
    .then(response => {
      if(response.ok) {
        //success
        alert(response);
      } else {
        throw Error('Server error');
      }
    })
    .catch(error => {
       console.log('fail', error);
    });
GetoX
  • 4,225
  • 2
  • 33
  • 30
1

This is a solution that I implemented

var formData = new FormData();
var files = $('input[type=file]');
for (var i = 0; i < files.length; i++) {
if (files[i].value == "" || files[i].value == null) {
    return false;
}
else {
    formData.append(files[i].name, files[i].files[0]);
}
}
var formSerializeArray = $("#Form").serializeArray();
for (var i = 0; i < formSerializeArray.length; i++) {
  formData.append(formSerializeArray[i].name, formSerializeArray[i].value)
}
$.ajax({
 type: 'POST',
 data: formData,
 contentType: false,
 processData: false,
 cache: false,
 url: '/Controller/Action',
 success: function (response) {
    if (response.Success == true) {
        return true;
    }
    else {
        return false;
    }
 },
 error: function () {
    return false;
 },
 failure: function () {
    return false;
 }
 });
keivan kashani
  • 1,263
  • 14
  • 15
0

---Solution for DOT NET CORE MVC Implementation---

While looking at this question I though I should right .NET CORE implementation for this because the question is not specific to any backend language. So guys here is the standalone implementation example.

Objective :- To submit form fields including files and how we can get data in a single model at backend

HTML Code / View Code - Views/Home/Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}

<input type="file" id="FileUpload1" multiple />
<div>
    <label>Enter First Name :</label>
    <input type="text" id="nameText" maxlength="50" />

</div>
<input type="button" id="btnUpload" value="Submit Form with Files" />

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
    $(document).ready(function () {
        $('#btnUpload').click(function () {

            // Checking whether FormData is available in browser
            if (window.FormData !== undefined) {

                var fileUpload = $("#FileUpload1").get(0);
                var files = fileUpload.files;

                // Create FormData object
                var fileData = new FormData();

                // Looping over all files and add it to FormData object
                for (var i = 0; i < files.length; i++) {
                    fileData.append("files", files[i]);
                }
                // Adding one more key to FormData object
                fileData.append('FirstName', $("#nameText").val());

                $.ajax({
                    url: '/Home/UploadFiles',
                    type: "POST",
                    contentType: false, // Not to set any content header
                    processData: false, // Not to process data
                    data: fileData,
                    success: function (result) {
                        alert(result);
                    },
                    error: function (err) {
                        alert(err.statusText);
                    }
                });
            } else {
                alert("FormData is not supported.");
            }
        });
    });
</script>  

Backend Code / Controller action method Controllers/HomeController.cs

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IWebHostEnvironment _environment;

    public HomeController(ILogger<HomeController> logger, IWebHostEnvironment environment)
    {
        _logger = logger;
        _environment = environment;
    }

    public IActionResult Index()
    {
        return View();
    }

    public IActionResult Privacy()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> UploadFiles(MyForm myForm)
    {
        var files = myForm.Files;
        // First Name 
        string name = myForm.FirstName;

        // check All files
        foreach (IFormFile source in files)
        {
            string filename = ContentDispositionHeaderValue.Parse(source.ContentDisposition).FileName.Trim('"');

            filename = this.EnsureCorrectFilename(filename);
            string fileWithPath = this.GetPathAndFilename(filename);
            // Create directory if not exist
            Directory.CreateDirectory(Path.GetDirectoryName(fileWithPath));

            using (FileStream output = System.IO.File.Create(fileWithPath))
                await source.CopyToAsync(output);
        }

        return Ok("Success");
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }

    public class MyForm
    {
        public string FirstName { get; set; }
        public IList<IFormFile> Files { get; set; }
    }

    private string EnsureCorrectFilename(string filename)
    {
        if (filename.Contains("\\"))
            filename = filename.Substring(filename.LastIndexOf("\\") + 1);

        return filename;
    }

    private string GetPathAndFilename(string filename)
    {
        return Path.Combine(_environment.ContentRootPath, "uploadedFiles", filename);
    }
}

Full Source Code Repo: https://github.com/rj-learning/DotNetCoreFileUpload

Rohit Jadhav
  • 1,035
  • 2
  • 11
  • 14
-1

In my case I had to make a POST request, which had information sent through the header, and also a file sent using a FormData object.

I made it work using a combination of some of the answers here, so basically what ended up working was having this five lines in my Ajax request:

 contentType: "application/octet-stream",
 enctype: 'multipart/form-data',
 contentType: false,
 processData: false,
 data: formData,

Where formData was a variable created like this:

 var file = document.getElementById('uploadedFile').files[0];
 var form = $('form')[0];
 var formData = new FormData(form);
 formData.append("File", file);
ndarriulat
  • 749
  • 1
  • 9
  • 11
  • 1
    `contentType: "application/octet-stream",` is actively harmful and the only reason it doesn't cause a problem is because you overwrite it two lines later. – Quentin Apr 30 '19 at 13:12
  • 1
    `enctype: 'multipart/form-data',` is pointless. jQuery.ajax doesn't recognise that paramater. – Quentin Apr 30 '19 at 13:12
  • … the rest of your answer fails to cover the "data" bit of "data **and** files" from the question title. – Quentin Apr 30 '19 at 13:19
-1

you can just append them on your formdata, add your files and datas in it.you can read this..

https://developer.mozilla.org/en-US/docs/Web/API/FormData/append

for better understanding. you can separately retrieve them $_FILES for your files and $_POST for your data.

ryuhk
  • 9
  • 1
-3
<form id="form" method="post" action="otherpage.php" enctype="multipart/form-data">
    <input type="text" name="first" value="Bob" />
    <input type="text" name="middle" value="James" />
    <input type="text" name="last" value="Smith" />
    <input name="image" type="file" />
    <button type='button' id='submit_btn'>Submit</button>
</form>

<script>
$(document).on("click", "#submit_btn", function (e) {
    //Prevent Instant Click  
    e.preventDefault();
    // Create an FormData object 
    var formData = $("#form").submit(function (e) {
        return;
    });
    //formData[0] contain form data only 
    // You can directly make object via using form id but it require all ajax operation inside $("form").submit(<!-- Ajax Here   -->)
    var formData = new FormData(formData[0]);
    $.ajax({
        url: $('#form').attr('action'),
        type: 'POST',
        data: formData,
        success: function (response) {
            console.log(response);
        },
        contentType: false,
        processData: false,
        cache: false
    });
    return false;
});
</script>

///// otherpage.php

<?php
    print_r($_FILES);
?>
Wang Liang
  • 4,244
  • 6
  • 22
  • 45
Shailesh Dwivedi
  • 657
  • 7
  • 15