Using the JavaScript FileReader API to Avoid File Upload Limits


If you’ve ever spent any amount of time messing with PHP configuration files to get a file to upload, you know that uploading large files can be a real pain. You have to find the loaded php.ini file, edit the upload_max_filesize and post_max_size settings, and hope that you never have to change servers and do all of this over again.

I ran into this problem myself while working on WP Migrate DB Pro – one of the features that will be going into the next release is the ability to upload and import an SQL file. Since WP Migrate DB Pro is used on a wide variety of servers, I needed to create a reliable upload tool that can upload large files without hitting upload limits.

Meet the JavaScript FileReader API. It’s an easy way to read and process any sort of file without the need to upload the file to the server first. FileReader is now supported in all major browsers including Internet Explorer 10, making it a viable solution for just about any project.

With that in mind, let’s create a sample WordPress file upload plugin to take a look at the FileReader API and learn how to use it to process large file uploads.

Getting Started

Since the FileReader API is baked right into JavaScript, the HTML side of things is very simple and relies on a basic HTML file input field:

    <input type="file" name="dbi_import_file" /><br><br>
    <input type="submit" value="Upload" />

To make things easier we’re going to create a small class to contain most of our code and place the above form inside a WordPress dashboard widget:

 * Plugin Name: DBI File Uploader
 * Description: Upload large files using the JavaScript FileReader API
 * Author: Delicious Brains Inc
 * Version: 1.0
 * Author URI:
 * Plugin URI:

class DBI_File_Uploader {

    public function __construct() {
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
        add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) );
        add_action( 'wp_ajax_dbi_upload_file', array( $this, 'ajax_upload_file' ) );

    public function enqueue_scripts() {
        $src = plugins_url( 'dbi-file-uploader.js', __FILE__ );
        wp_enqueue_script( 'dbi-file-uploader', $src, array( 'jquery' ), false, true );
        wp_localize_script( 'dbi-file-uploader', 'dbi_vars', array(
            'upload_file_nonce' => wp_create_nonce( 'dbi-file-upload' ),

    public function add_dashboard_widget() {
        wp_add_dashboard_widget( 'dbi_file_upload', 'DBI File Upload', array( $this, 'render_dashboard_widget' ) );

    public function render_dashboard_widget() {
            <p id="dbi-upload-progress">Please select a file and click "Upload" to continue.</p>

            <input id="dbi-file-upload" type="file" name="dbi_import_file" /><br><br>

            <input id="dbi-file-upload-submit" class="button button-primary" type="submit" value="Upload" />

With the upload form in place we should see a very basic file upload form when we visit the WordPress dashboard:

Screenshot of upload form

Uploading the File

The form above doesn’t do anything yet, so let’s create the dbi-file-uploader.js file that was enqueued above and add a simple click handler for the upload button. After initializing the FileReader object and selecting the file to be uploaded, it will call the upload_file() function to start the upload:

(function( $ ) {

    var reader = {};
    var file = {};
    var slice_size = 1000 * 1024;

    function start_upload( event ) {

        reader = new FileReader();
        file = document.querySelector( '#dbi-file-upload' ).files[0];

        upload_file( 0 );
    $( '#dbi-file-upload-submit' ).on( 'click', start_upload );

    function upload_file( start ) {


})( jQuery );

Now we can start working on the upload_file() function that will handle most of the heavy lifting. First we create a blob containing a small chunk of the file using the JavaScript slice() method:

function upload_file( start ) {
    var next_slice = start + slice_size + 1;
    var blob = file.slice( start, next_slice );

We’ll also need to define a function within the upload_file() function that will run when the FileReader API has read from the file.

reader.onloadend = function( event ) {
    if ( !== FileReader.DONE ) {

    // At this point the file data is loaded to

Now we need to tell the FileReader API to read a portion of the file. We can do that by passing the blob of file data that we created to the FileReader object:

reader.readAsDataURL( blob );

It’s worth noting that we’re using the FileReader.readAsDataURL() method of the FileReader object, instead of the FileReader.readAsText() or FileReader.readAsBinaryString() methods that are mentioned in the FileReader documentation.

In this case the FileReader.readAsDataURL() method is much more reliable than the other methods because the contents of the file are read out as a Base64-encoded string as opposed to plain text or binary. This is important because a string containing plain text or binary will likely run into encoding or sanitization issues when sent to the server via AJAX. On the other hand, a Base64-encoded string will usually just contain the A-Z, a-z, and 0-9 characters, and is easy enough to decode with PHP or any other server-side language.

Let’s fill out the rest of the function by adding the AJAX call that is responsible for POSTing the data to the server and recursively calling the upload_file() function again when the request has completed. Here’s what the upload_file() function looks like in it’s entirety:

function upload_file( start ) {
    var next_slice = start + slice_size + 1;
    var blob = file.slice( start, next_slice );

    reader.onloadend = function( event ) {
        if ( !== FileReader.DONE ) {

        $.ajax( {
            url: ajaxurl,
            type: 'POST',
            dataType: 'json',
            cache: false,
            data: {
                action: 'dbi_upload_file',
                file_type: file.type,
                nonce: dbi_vars.upload_file_nonce
            error: function( jqXHR, textStatus, errorThrown ) {
                console.log( jqXHR, textStatus, errorThrown );
            success: function( data ) {
                var size_done = start + slice_size;
                var percent_done = Math.floor( ( size_done / file.size ) * 100 );

                if ( next_slice < file.size ) {
                    // Update upload progress
                    $( '#dbi-upload-progress' ).html( 'Uploading File - ' + percent_done + '%' );

                    // More to upload, call function recursively
                    upload_file( next_slice );
                } else {
                    // Update upload progress
                    $( '#dbi-upload-progress' ).html( 'Upload Complete!' );
        } );

    reader.readAsDataURL( blob );

It’s still relatively simple in terms of functionality, but that should be enough to get the file upload going on the client side.

Saving Chunks Server-Side

Now that JavaScript has handled splitting the file up and POST-ing the file to the server, we need to re-assemble and save those chunks with PHP. To do that, we’re going to add the ajax_upload_file() method to our main plugin class:

public function ajax_upload_file() {
    check_ajax_referer( 'dbi-file-upload', 'nonce' );

    $wp_upload_dir = wp_upload_dir();
    $file_path     = trailingslashit( $wp_upload_dir['path'] ) . $_POST['file'];
    $file_data     = $this->decode_chunk( $_POST['file_data'] );

    if ( false === $file_data ) {

    file_put_contents( $file_path, $file_data, FILE_APPEND );


public function decode_chunk( $data ) {
    $data = explode( ';base64,', $data );

    if ( ! is_array( $data ) || ! isset( $data[1] ) ) {
        return false;

    $data = base64_decode( $data[1] );
    if ( ! $data ) {
        return false;

    return $data;

The example above is about as simple as it gets – the ajax_upload_file() method does a quick nonce check and then decodes the data via decode_chunk(). If the data can be successfully decoded from the Base64-encoded data URL, it is appended to the file and the upload continues.

With that in place we should be able to run our uploader and the file should get saved to the path we designated:

The completed file uploader

And that’s it, we have a working (although relatively basic) large file uploader! If you’ve been following along and want to see the full code, I’ve uploaded it to GitHub so you can take a look.


I really like how relatively simple it is to create a file uploader that can handle huge files without needing to adjust any settings server-side. It’s always interesting when technologies that were just pipe dreams a few years ago become commonplace and greatly improve today’s workflows.

It’s worth noting that there are several existing JavaScript libraries like FineUploader and jQuery File Upload that can also upload large files, and in many cases, it makes more sense to use an existing library instead of reinventing the wheel. At the same time, it never hurts to have at least a basic understanding of what is going on behind the scenes in case you ever need to fix a bug or implement a new feature.

If you’re going to implement something similar to this in a production application, you would definitely want to research any potential security implications. This could include additional client-side and server-side file type validation, disallowing uploads for files with executable file extensions, and making sure that uploaded files have a random string in the filename to mitigate some potential vulnerabilities. You may also want to implement a way for users to pause or cancel the upload and come back to it later, and log any errors that come up during the upload.

Have you ever written a script to handle large file uploads? If so, did you take a similar approach using the FileReader API or did you use something completely different? Let me know in the comments below.

About the Author

Matt Shaw

Matt is a WordPress plugin developer located near Philadelphia, PA. He loves to create awesome new tools with PHP, Javascript, and whatever else he happens to get his hands on.