I have a custom taxonomy and I want to add a custom field inside , OK that's seems to be easy but the hardest part is when I want to add a file input type the enctype="multipart/form-data" I already add it to the form .


1 Answer 1


Updated 06/06/2016: to use term_meta implemented into WordPress 4.4 but I haven't tested the updated code, if there's issues leave a comment below.

Here's what I do to add an upload field to my taxonomy terms, in this example I add it to the built in Posts Categories but you could modify it to add in any taxonomy.

Step One - Make sure we're using a enctype on our form:

function edit_form_tag( ) {
    echo ' enctype="multipart/form-data"';
add_action( 'category_term_edit_form_tag' , 'edit_form_tag' );
add_action( '{TAXONOMY_HERE}_term_edit_form_tag' , 'edit_form_tag' );

You'll have to replace {TAXONOMY_HERE} with your actual taxonomy name, for example if I had a custom taxonomy called "tax_projects" the action would look like:

add_action( 'tax_projects_term_edit_form_tag' , 'edit_form_tag' );

Step Two - Add our upload field to our terms:

This is the relatively easy part but is lengthy because of all the data we need to output. I will first show the upload and output code but not the deletion code which will be added in at the end of this post as it is AJAX. It is heavily commented so feel free to remove comments whenever you see fit.

/** Add New Field To Category **/
function additional_category_fields( $term, $tax ) {
    $uploadID   = get_term_meta( $term->term_id, '_term_image', true );             // Retrieve our Attachment ID from the post_meta Database Table
    $feedback   = get_term_meta( $term->term_id, '_term_image_feedback', true );    // Retrieve any upload feedback from the Optoins Database Table

    <tr class="form-field">
        <th scope="row" valign="top"><label for="meta-order"><?php _e( 'Category Image' ); ?></label></th>
            <div id="catImage">

                <!-- Create a nonce to validate against -->
                <input type="hidden" name="upload_meta_nonce" value="<?php echo wp_create_nonce( basename( __FILE__ ) ); ?>" />

                <!-- Define our actual upload field -->
                Please choose an image: <input type="file" name="_uploaded_file" value="" />

                  if( is_numeric( $uploadID ) ) :                                       // IF our upload ID is actually numeric, proceed

                    /*  In this case we are pulling an image, if we are uploading
                    /*  something such as a PDF we could use the built-in function
                    /*  wp_get_attachment_url( $id );
                    /*  codex.wordpress.org/Function_Reference/wp_get_attachment_url
                    $imageArr = wp_get_attachment_image_src( $uploadID, 'medium' );     // Get the URL of the medium sized image
                    $imageURL = $imageArr[0];                                           // wp_get_attachment_image_src() returns an array, index 0 is our URL

                    <div id="uploaded_image">
                        <a href="post.php?post=<?php echo $uploadID; ?>&action=edit" target="_blank">Edit Image</a><br />

                        <!-- Display our image using the URL retrieved earlier -->
                        <a href="post.php?post=<?php echo $uploadID; ?>&action=edit" target="_blank"><img src="<?php echo $imageURL; ?>" /></a><br /><br />

                <!-- IF we received feedback, something went wrong and we need to show that feedback. -->               
                <?php elseif( ! empty( $feedback ) ) : ?>

                    <p style="color:red;font-size:12px;font-weight;bold;font-style:italic;"><?php echo $feedback; ?></p>

                <?php endif; ?>

            <span class="description"><?php _e( 'Upload an appropriate image.' ); ?></span>
                <br />
                <br />

            <!-- This link is for our deletion process -->
            <?php if( ! empty( $uploadID ) ) : ?>

                <a href="javascript:void(0)" class="deleteImage" style="color:red;text-decoration:underline;">Delete</a>

            <?php endif; ?>


    /** Since we've shown the user the feedback they need to see, we can delete our meta **/
    delete_term_meta( $term->term_id, '_term_image_feedback' );
add_action( 'category_edit_form_fields', 'additional_category_fields', 10, 2 ); 

Hopefully the comments explain anything that may seem strange but a brief explanation of what we're doing is here we're showing the upload field. We ( are going to ) save our attachment IDs as term meta. If there is something uploaded we can retrieve the attachment ID and show it to the user it's up to you to decide it it's going to be an image or a file - in this example it's an image.

Step Three - Upload our attachments and save our data

The fun part! Here we'll run through some sanitization and make sure that the user is uploading the correct type of file, otherwise we'll let them know using feedback. Again this is a heavily commented code with a brief explanation at the end.

/** Save Category Meta **/
function save_category_fields( $term_id ) {

    // Make sure that the nonce is set, taxonomy is set, and that our uploaded file is not empty
      isset( $_POST['upload_meta_nonce'] ) && wp_verify_nonce( $_POST['upload_meta_nonce'], basename( __FILE__ ) ) &&
      isset( $_POST['taxonomy'] ) && isset( $_FILES['_uploaded_file'] ) && !empty( $_FILES['_uploaded_file'] )
    ) {
        $supportedTypes = array( 'image/gif', 'image/jpeg', 'image/png' );                      // Only accept image mime types. - List of mimetypes: http://en.wikipedia.org/wiki/Internet_media_type
        $fileArray      = wp_check_filetype( basename( $_FILES['_uploaded_file']['name'] ) );   // Get the mime type and extension.
        $fileType       = $fileArray['type'];                                                   // Store our file type

        // Verify that the type given is what we're expecting
        if( in_array( $fileType, $supportedTypes ) ) {
            $uploadStatus = wp_handle_upload( $_FILES['_uploaded_file'], array( 'test_form' => false ) );   // Let WordPress handle the upload

            // Make sure that the file was uploaded correctly, without error
            if( isset( $uploadStatus['file'] ) ) {
                require_once(ABSPATH . "wp-admin" . '/includes/image.php');

                // Let's add the image to our media library so we get access to metadata
                $imageID = wp_insert_attachment( array(
                        'post_mime_type'    => $uploadStatus['type'],
                        'post_title'        => preg_replace( '/\.[^.]+$/', '', basename( $uploadStatus['file'] ) ),
                        'post_content'      => '',
                        'post_status'       => 'publish'

                // Generate our attachment metadata then update the file.
                $attachmentData = wp_generate_attachment_metadata( $imageID, $uploadStatus['file'] );
                wp_update_attachment_metadata( $imageID,  $attachmentData );

                $existingImage = get_term_meta( $term_id, '_term_image', true );        // IF a file already exists in this meta, grab it
                if( ! empty( $existingImage ) && is_numeric( $existingImage ) ) {       // IF the meta does exist, delete it.
                    wp_delete_attachment( $existingImage );

                update_term_meta( $term_id, '_term_image', $imageID );                  // Update our meta with the new attachment ID
                delete_term_meta( $term_id, '_term_image_feedback' );                   // Just in case there's a feedback meta, delete it - theoretically it shouldn't exist at this point.
            else {
                $uploadFeedback = 'There was a problem with your uploaded file. Contact Administrator.';    // Something major went wrong, enable debugging
        else {
            $uploadFeedback = 'Image Files only: JPEG/JPG, GIF, PNG';   // Wrong file type

        // Update our Feedback meta
        if( isset( $uploadFeedback ) ) {
            update_term_meta( $term_id, '_term_image_feedback', $uploadFeedback );
add_action ( 'edited_category', 'save_category_fields');

So above we need to make sure we're in the correct spot with the correct data, we need a taxonomy name to use the unique naming convention, we also need to make sure we have an uploaded file. Once we know this we can use some built in function to get the actual file type and check it against the specific types that we need the user to upload. Once that's done it's smooth sailing - upload the file, generate metadata, and update our term meta. The _term_image meta will actually hold an Image ID and feedback will hold a string.

Andddddd you're done. That should allow the user to upload an image ( or you may modify it to upload a file ) of which you can get by calling: get_term_meta( $term->term_id, '_term_image, true );. The only thing we're missing is allowing the user to delete the image

Step Four - Deleting the uploaded image

Normally I do not like mixing JS and PHP together but in this case I couldn't find a way around it. At the top of the above additional_category_fields function we need to add some Javascript, since JQuery is already enqueued in the admin panel we can just inline it:

<script type="text/javascript">
    jQuery(function($) {

        /** Add our listener to the delete button **/

            /** Make sure the user didn't hit the button by accident and they really mean to delete the image **/
            if( $( '#uploaded_image' ).length > 0 && confirm( 'Are you sure you want to delete this file?' ) ) {
                var result = $.ajax({
                    url: '/wp-admin/admin-ajax.php',
                    type: 'GET',
                    data: {
                        action: 'tax_del_image',
                        term_id: '<?php echo $term->term_id; ?>',
                        taxonomy: '<?php echo $tax; ?>'
                    dataType: 'text'

                result.success( function( data ) {
                result.fail( function( jqXHR, textStatus ) {
                    console.log( "Request failed: " + textStatus );

Here using ajax to pass the current term_id and current taxonomy to our function called tax_del_image. The PHP function looks like this:

/** Metabox Delete Image **/
function tax_del_image() {

    /** If we don't have a term_id, bail out **/
    if( ! isset( $_GET['term_id'] ) ) {
        echo 'Not Set or Empty';

    $term_id = $_GET['term_id'];
    $imageID = get_term_meta( $term_id, '_term_image', true );  // Get our attachment ID

    if( is_numeric( $imageID ) ) {                              // Verify that the attachment ID is indeed a number
        wp_delete_attachment( $imageID );                       // Delete our image
        delete_term_meta( $term_id, '_term_image' );                // Delete our image meta
        delete_term_meta( $term_id, '_term_image_feedback' );   // Delete our feedback in the off-chance it actually exists ( it shouldn't )

    echo 'Contact Administrator';   // If we've reached this point, something went wrong - enable debugging
add_action( 'wp_ajax_tax_del_image', 'tax_del_image' );

Step Five - Cleanup

As of WordPress 4.1 we have a nice pre_delete_term function which allows us to cleanup our database whenever a term is deleted.

/** Delete Associated Media Upon Term Deletion **/
function delete_associated_term_media( $term_id, $tax ){
    global $wp_taxonomies; 

    if( isset( $term_id, $tax, $wp_taxonomies ) && isset( $wp_taxonomies[$tax] ) ) {
        $imageID = get_term_meta( $term_id, '_term_image', true );

        if( is_numeric( $imageID ) ) {
            wp_delete_attachment( $imageID );
add_action( 'pre_delete_term', 'delete_associated_term_media', 10, 2 );

This function will take care of the deletion, quick and simple. Combine everything, slap it into a functions.php file ( or convert it into a plugin, or an external file, etc. ) and you have yourself an upload field for terms! Easy Peezy!

Bonus - Term Update Redirection

So as it stands right now, terms redirect back to the parent "Add Term" page whenever it gets updated - should you find this annoying user birgire has a solution - Modify Term Update Redirection

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.