jQuery AJAX file upload to server

How to upload any files, for example, images to the server using AJAX and jQuery? It is done quite simply! Below we will discuss everything in detail.

In those "ancient" times, when jQuery didn't exist, or maybe it did, but browsers were not as advanced, uploading a file to a website using AJAX was a tedious task: through various hacks like iframes. I didn't experience that time, and who is interested in it now? What is interesting now is that saving files to a website is done very easily. Even someone without experience and understanding of how AJAX works can quickly figure out what goes where. And this article will help them. If we supplement these capabilities with WordPress functions, then safe processing and uploading of files to the server becomes quite trivial and even interesting (see the WordPress example at the end of the article).

However, no matter how simple it seems, it should be noted that minimal experience with files and basic knowledge of Javascript, jQuery, and PHP are still necessary! At a minimum, one should understand how files are uploaded to the server, how AJAX works in general, and one should be able to read and understand code a little.

The method described below is quite stable and essentially relies on the Javascript object new FormData(), which is supported in all browsers.

For better understanding of the material, it is divided into steps. That's it, let's go...

AJAX File Upload: General Example

It all starts with having an input field of type file on the website. There is no need for this field to be part of a form (the <form> tag).

Thus, we have HTML code with a file field and a button "Upload Files".

<input type="file" multiple="multiple" accept=".txt,image/*">
<a href="#" class="upload_files button">Upload Files</a>
<div class="ajax-reply"></div>

Step 1. Data from the file field

The first step is to get the data of the files to be uploaded.

When clicking on the file field, a file selection window appears, after selecting, the data about them is saved in the input field, and we need to "take" them from there. For this, we will attach a JS function to the change event that will save the existing data from the file field into the JS variable files:

var files; // variable. will contain file data

// fill the variable with data when the value of the file field changes
jQuery('input[type=file]').on('change', function(){
	files = this.files;
});

Step 2. Create AJAX request (on click)

We have the file data, now we need to send it via AJAX. We attach this event to the click on the button "Upload Files".

At the moment of clicking, we create a new object new FormData() and add the data from the files variable into it. Using FormData(), we will ensure that the sent data looks as if we simply submitted a form in the browser.

Next, from the available form data, we create a non-standard AJAX request, in which we pass the files in the standard format for the server: $_FILES.

To make such a request, in jQuery we need to specify additional AJAX parameters, so the familiar function $.post() is not suitable, and we use a more flexible alternative: $.ajax().

Two important additional parameters need to be set to false:

processData
Disables processing of the transmitted data. By default, for example, for GET requests, jQuery collects the data into a query string and adds this string to the end of the URL. For POST data, it performs other transformations. Any changes to the original data will hinder us, so we disable this option...
contentType
Disables setting the request type header. The default setting for jQuery is "application/x-www-form-urlencoded". This header does not allow file uploads. If this parameter is set to "multipart/form-data", PHP still cannot recognize the transmitted data and will output a warning "Missing boundary in multipart/form-data"... In general, it's easiest to disable this option, and then everything works!
// handling and sending AJAX request on click of the upload_files button
jQuery('.upload_files').on( 'click', function( event ){

	event.stopPropagation(); // stop all current JS events
	event.preventDefault();  // stop the default event for the current element - click for the <a> tag

	// do nothing if files is empty
	if( typeof files == 'undefined' ) return;

	// create a form data object
	var data = new FormData();

	// fill the data object with files in the appropriate format for sending
	$.each( files, function( key, value ){
		data.append( key, value );
	});

	// add a variable for identifying the request
	data.append( 'my_file_upload', 1 );

	// AJAX request
	$.ajax({
		url         : './submit.php',
		type        : 'POST', // important!
		data        : data,
		cache       : false,
		dataType    : 'json',
		// disable processing of transmitted data, let it be sent as is
		processData : false,
		// disable setting the request type header. This way jQuery tells the server that this is a string request
		contentType : false,
		// function for successful server response
		success     : function( respond, status, jqXHR ){

			// OK - files uploaded
			if( typeof respond.error === 'undefined' ){
				// output the paths of uploaded files in the '.ajax-reply' block
				var files_path = respond.files;
				var html = '';
				$.each( files_path, function( key, val ){
					 html += val +'<br>';
				} )

				$('.ajax-reply').html( html );
			}
			// error
			else {
				console.log('ERROR: ' + respond.data );
			}
		},
		// function for error response from the server
		error: function( jqXHR, status, errorThrown ){
			console.log( 'AJAX request ERROR: ' + status, jqXHR );
		}

	});

});

Step 3. Process the request: upload files to the server

Now the last step: we need to process the sent request.

To make it clear, we will process the request without additional checks for files, i.e., simply save the received files to the required folder. However, for security, the uploaded files must be checked, at least for the extension (type) of the file...

We will create a file submit.php with the following code (it is assumed that submit.php is in the same folder as the file from which the AJAX request is sent):

<?php

if( isset( $_POST['my_file_upload'] ) ){
	// IMPORTANT! here should be all security checks for the transmitted files and output errors if needed

	$uploaddir = './uploads'; // . - the current folder where submit.php is located

	// create the folder if it doesn't exist
	if( ! is_dir( $uploaddir ) ) mkdir( $uploaddir, 0777 );

	$files      = $_FILES; // received files
	$done_files = array();

	// move files from the temporary directory to the specified one
	foreach( $files as $file ){
		$file_name = $file['name'];

		if( move_uploaded_file( $file['tmp_name'], "$uploaddir/$file_name" ) ){
			$done_files[] = realpath( "$uploaddir/$file_name" );
		}
	}

	$data = $done_files ? array('files' => $done_files ) : array('error' => 'File upload error.');

	die( json_encode( $data ) );
}

That's all!

Important! This code only shows how to receive and save files. In reality, you need to check the formats of the accepted files, their size, transliterate Cyrillic names, and possibly perform some other checks.

-

AJAX File Upload: Example for WordPress

For WordPress, processing AJAX requests is much easier because there are ready-made functions, for example, media_handle_upload().

The first and second steps are similar, and in the third step, we will use a built-in function that will add the file to the media library and associate it with the current post.

To make the code below work, it needs to be added to the functions.php file of the theme. Then, create a page with the slug ajax_file_upload and visit that page. In the content, you will see a form for adding a file. Choose files and check if everything uploaded...

This is a full example of how to safely upload files to the server in the WordPress environment.

<?php

// form
add_action( 'the_content', 'ajax_file_upload_html' );

// script
add_action( 'wp_footer', 'ajax_file_upload_jscode' );

// AJAX handler
add_action( 'wp_ajax_'.'ajax_fileload',        'ajax_file_upload_callback' );
add_action( 'wp_ajax_nopriv_'.'ajax_fileload', 'ajax_file_upload_callback' );

// HTML code of the form
function ajax_file_upload_html( $text ){
	// exit if not our page...
	if( $GLOBALS['post']->post_name !== 'ajax_file_upload' )
		return $text;

	return $text .= '
		<input type="file" multiple="multiple" accept="image/*">
		<button class="upload_files">Upload File</button>
		<div class="ajax-reply"></div>
	';
}

// JS code
function ajax_file_upload_jscode(){
	?>
	<script>
		jQuery(document).ready(function($){

			// link to the AJAX handler file
			var ajaxurl = '<?= admin_url('admin-ajax.php') ?>';
			var nonce   = '<?= wp_create_nonce('uplfile') ?>';

			var files; // variable. will contain file data

			// fill the variable with data when the value of the file field changes
			$('input[type=file]').on('change', function(){
				files = this.files;
			});

			// handling and sending AJAX request on click of the upload_files button
			$('.upload_files').on( 'click', function( event ){

				event.stopPropagation(); // stop all current JS events
				event.preventDefault();  // stop the default event for the current element - click for the <a> tag

				// do nothing if files is empty
				if( typeof files == 'undefined' ) return;

				// create file data in the appropriate format for sending
				var data = new FormData();
				$.each( files, function( key, value ){
					data.append( key, value );
				});

				// add a variable identifier for the request
				data.append( 'action', 'ajax_fileload' );
				data.append( 'nonce', nonce );
				let pid_match = $(document.body).attr('class').match( /postid-([0-9]+)/ );
				data.append( 'post_id', pid_match ? pid_match[1] : 0 );

				var $reply = $('.ajax-reply');

				// AJAX request
				$reply.text( 'Uploading...' );
				$.ajax({
					url         : ajaxurl,
					type        : 'POST',
					data        : data,
					cache       : false,
					dataType    : 'json',
					// disable processing of transmitted data, let it be sent as is
					processData : false,
					// disable setting the request type header. This way jQuery tells the server that this is a string request
					contentType : false,
					// function for successful server response
					success     : function( respond, status, jqXHR ){
						// OK
						if( respond.success ){
							$.each( respond.data, function( key, val ){
								$reply.append( '<p>'+ val +'</p>' );
							} );
						}
						// error
						else {
							$reply.text( 'ERROR: ' + respond.error );
						}
					},
					// function for error response from the server
					error: function( jqXHR, status, errorThrown ){
						$reply.text( 'AJAX request ERROR: ' + status );
					}

				});

			});

		})
	</script>
	<?php
}

// AJAX request handler
function ajax_file_upload_callback(){
	check_ajax_referer( 'uplfile', 'nonce' ); // protection

	if( empty( $_FILES ) )
		wp_send_json_error( 'No files...' );

	$post_id = (int) $_POST['post_id'];

	// limit the size of the uploaded image
	$sizedata = getimagesize( $_FILES['upfile']['tmp_name'] );
	$max_size = 2000;
	if( $sizedata[0]/*width*/ > $max_size || $sizedata[1]/*height*/ > $max_size )
		wp_send_json_error( __('The image cannot be larger than '. $max_size .'px in width or height...','km') );

	// process the file upload
	require_once ABSPATH . 'wp-admin/includes/image.php';
	require_once ABSPATH . 'wp-admin/includes/file.php';
	require_once ABSPATH . 'wp-admin/includes/media.php';

	// filter acceptable file types - allow only images
	add_filter( 'upload_mimes', function( $mimes ){
		return [
			'jpg|jpeg|jpe' => 'image/jpeg',
			'gif'          => 'image/gif',
			'png'          => 'image/png',
		];
	} );

	$uploaded_imgs = array();

	foreach( $_FILES as $file_id => $data ){
		$attach_id = media_handle_upload( $file_id, $post_id );

		// error
		if( is_wp_error( $attach_id ) )
			$uploaded_imgs[] = 'Error uploading file `'. $data['name'] .'`: '. $attach_id->get_error_message();
		else
			$uploaded_imgs[] = wp_get_attachment_url( $attach_id );
	}

	wp_send_json_success( $uploaded_imgs );

}