In a couple of recent projects I have been delving deeper and deeper in to customising the functionality of WordPress, using it more like a CMS than just a blogging platform.

I was comfortable in adding new custom post types and associating custom fields to these new data sets. However, there was one type of functionality I wasn’t entirely happy with – adding custom file uploads. I have used the tremendously useful Attachments plugin which was written and is actively maintained by Jonathan Christopher (who also writes regularly about WordPress development among other things on his website Monday by Noon). While this solution is good for attaching multiple files to an entry, I needed more control, such as named types and limiting the number of attachments, e.g. one per type.

Once I understood the concept on how to the Attachments plugin works, it was a case of dissecting the code and using what I needed for my solution. The concept is pretty simple — upload a file to the media area, associate that file with the post by using a (hidden) custom field and the newly created attachment_id.

The Attachments plugin triggers the built in media upload functionality, where you can upload new files or choose one from the existing library. Now the clever bit – the plugin uses some JavaScript to listen to a click event on the “Insert into Post” button, preventing the default behaviour, finding the attachment_id value of the file and inserting this into a hidden form input.

After researching the plugin’s behaviour, it was fairly straight-forward to replicate this functionality. On the front-end you use the custom field value in the built-in WordPress attachment functions to get the uploaded file’s information, e.g. wp_get_attachment_url on the custom field will return URL of the uploaded file.

I have used this solution on a couple of projects and although it uses the built in file upload behaviour, it can be a bit convoluted when all you want to do it associate a file with a post. So I decided to investigate using a simple file upload using the default browser control, while still making use of the WordPress file management and media library. This turned out to be relatively easy, but there are a few gotchas.

A simple upload solution

By default the post edit form in the WordPress admin does not have the enctype="multipart/form-data" attribute which is required for file uploading. Luckily there is an action which allows you add custom attributes to the edit form. The following code adds the enctype attribute to all post edit forms (you can easily modify it to be restricted to a certain post type).

add_action('post_edit_form_tag', 'post_edit_form_tag');
function post_edit_form_tag() {
    echo ' enctype="multipart/form-data"';
}

Next you need to add a add_meta_box for the file upload field. This is done within a function that is hooked in to WordPress using add_action('admin_init');. Below is the function I use for the custom field document upload:

function custom_field_document_upload() {
    global $post;

    $custom         = get_post_custom($post->ID);
    $download_id    = get_post_meta($post->ID, 'document_file_id', true);

    echo '<p><label for="document_file">Upload document:</label><br />';
    echo '<input type="file" name="document_file" id="document_file" /></p>';
    echo '</p>';

    if(!empty($download_id) && $download_id != '0') {
        echo '<p><a href="' . wp_get_attachment_url($download_id) . '">
            View document</a></p>';
    }
}

The final functionality is to save the uploaded file as an attachment and associate it with the post. This is done by using a function that hooks in to WordPress using add_action('save_post');. The function looks for upload files, use the built in functions to upload, insert the attachment and associate the custom field using post meta.

function custom_field_document_update($post_id) {
    global $post;

    if(strtolower($_POST['post_type']) === 'page') {
        if(!current_user_can('edit_page', $post_id)) {
            return $post_id;
        }
    }
    else {
        if(!current_user_can('edit_post', $post_id)) {
            return $post_id;
        }
    }

    if(!empty($_FILES['document_file'])) {
        $file   = $_FILES['document_file'];
        $upload = wp_handle_upload($file, array('test_form' => false));
        if(!isset($upload['error']) && isset($upload['file'])) {
            $filetype   = wp_check_filetype(basename($upload['file']), null);
            $title      = $file['name'];
            $ext        = strrchr($title, '.');
            $title      = ($ext !== false) ? substr($title, 0, -strlen($ext)) : $title;
            $attachment = array(
                'post_mime_type'    => $wp_filetype['type'],
                'post_title'        => addslashes($title),
                'post_content'      => '',
                'post_status'       => 'inherit',
                'post_parent'       => $post->ID
            );

            $attach_key = 'document_file_id';
            $attach_id  = wp_insert_attachment($attachment, $upload['file']);
            $existing_download = (int) get_post_meta($post->ID, $attach_key, true);

            if(is_numeric($existing_download)) {
                wp_delete_attachment($existing_download);
            }

            update_post_meta($post->ID, $attach_key, $attach_id);
        }
    }
}

Now you can upload files using a simple file input custom field and pull the uploaded attachment from the media library using the build in functions. This code has been built as a focused solution to a problem and can be tailored to different use cases.

If you want to upload multiple files, then it would make sense to loop through all upload $_FILES. This would be a simple alteration and would make an assumption of adding an _id suffix to the file name for the custom field post meta. You would wrap the above code in the following loop while removing the $file variable and using the updated attach_key variable;

foreach($_FILES as $key => $file) {
    $attach_key = $key . '_id';
}

I hope you find this a useful tutorial for uploading files as custom fields in WordPress. If you’ve got any other solutions, please leave a comment.