Adding an image upload field to categories

Creating, saving and updating taxonomy meta data

How to add an image field to the category field.

new-category-image

There are a lot of tutorials around for how to add fields to categories. However, these mostly use an old method where data is saved to the theme options. But since WordPress 4.4, it’s been possible to save meta data for taxonomies in a similar way to saving post data. Secondly, we wanted the ability to upload an image for the category using the default WordPress media manager. So we came up with the following class:

The whole thing

/**
 * Plugin class
 **/
if ( ! class_exists( 'CT_TAX_META' ) ) {

class CT_TAX_META {

  public function __construct() {
    //
  }
 
 /*
  * Initialize the class and start calling our hooks and filters
  * @since 1.0.0
 */
 public function init() {
   add_action( 'category_add_form_fields', array ( $this, 'add_category_image' ), 10, 2 );
   add_action( 'created_category', array ( $this, 'save_category_image' ), 10, 2 );
   add_action( 'category_edit_form_fields', array ( $this, 'update_category_image' ), 10, 2 );
   add_action( 'edited_category', array ( $this, 'updated_category_image' ), 10, 2 );
   add_action( 'admin_footer', array ( $this, 'add_script' ) );
 }
 
 /*
  * Add a form field in the new category page
  * @since 1.0.0
 */
 public function add_category_image ( $taxonomy ) { ?>
   <div class="form-field term-group">
     <label for="category-image-id"><?php _e('Image', 'hero-theme'); ?></label>
     <input type="hidden" id="category-image-id" name="category-image-id" class="custom_media_url" value="">
     <div id="category-image-wrapper"></div>
     <p>
       <input type="button" class="button button-secondary ct_tax_media_button" id="ct_tax_media_button" name="ct_tax_media_button" value="<?php _e( 'Add Image', 'hero-theme' ); ?>" />
       <input type="button" class="button button-secondary ct_tax_media_remove" id="ct_tax_media_remove" name="ct_tax_media_remove" value="<?php _e( 'Remove Image', 'hero-theme' ); ?>" />
    </p>
   </div>
 <?php
 }
 
 /*
  * Save the form field
  * @since 1.0.0
 */
 public function save_category_image ( $term_id, $tt_id ) {
   if( isset( $_POST['category-image-id'] ) && '' !== $_POST['category-image-id'] ){
     $image = $_POST['category-image-id'];
     add_term_meta( $term_id, 'category-image-id', $image, true );
   }
 }
 
 /*
  * Edit the form field
  * @since 1.0.0
 */
 public function update_category_image ( $term, $taxonomy ) { ?>
   <tr class="form-field term-group-wrap">
     <th scope="row">
       <label for="category-image-id"><?php _e( 'Image', 'hero-theme' ); ?></label>
     </th>
     <td>
       <?php $image_id = get_term_meta ( $term -> term_id, 'category-image-id', true ); ?>
       <input type="hidden" id="category-image-id" name="category-image-id" value="<?php echo $image_id; ?>">
       <div id="category-image-wrapper">
         <?php if ( $image_id ) { ?>
           <?php echo wp_get_attachment_image ( $image_id, 'thumbnail' ); ?>
         <?php } ?>
       </div>
       <p>
         <input type="button" class="button button-secondary ct_tax_media_button" id="ct_tax_media_button" name="ct_tax_media_button" value="<?php _e( 'Add Image', 'hero-theme' ); ?>" />
         <input type="button" class="button button-secondary ct_tax_media_remove" id="ct_tax_media_remove" name="ct_tax_media_remove" value="<?php _e( 'Remove Image', 'hero-theme' ); ?>" />
       </p>
     </td>
   </tr>
 <?php
 }

/*
 * Update the form field value
 * @since 1.0.0
 */
 public function updated_category_image ( $term_id, $tt_id ) {
   if( isset( $_POST['category-image-id'] ) && '' !== $_POST['category-image-id'] ){
     $image = $_POST['category-image-id'];
     update_term_meta ( $term_id, 'category-image-id', $image );
   } else {
     update_term_meta ( $term_id, 'category-image-id', '' );
   }
 }

/*
 * Add script
 * @since 1.0.0
 */
 public function add_script() { ?>
   <script>
     jQuery(document).ready( function($) {
       function ct_media_upload(button_class) {
         var _custom_media = true,
         _orig_send_attachment = wp.media.editor.send.attachment;
         $('body').on('click', button_class, function(e) {
           var button_id = '#'+$(this).attr('id');
           var send_attachment_bkp = wp.media.editor.send.attachment;
           var button = $(button_id);
           _custom_media = true;
           wp.media.editor.send.attachment = function(props, attachment){
             if ( _custom_media ) {
               $('#category-image-id').val(attachment.id);
               $('#category-image-wrapper').html('<img class="custom_media_image" src="" style="margin:0;padding:0;max-height:100px;float:none;" />');
               $('#category-image-wrapper .custom_media_image').attr('src',attachment.sizes.thumbnail.url).css('display','block');
             } else {
               return _orig_send_attachment.apply( button_id, [props, attachment] );
             }
            }
         wp.media.editor.open(button);
         return false;
       });
     }
     ct_media_upload('.ct_tax_media_button.button'); 
     $('body').on('click','.ct_tax_media_remove',function(){
       $('#category-image-id').val('');
       $('#category-image-wrapper').html('<img class="custom_media_image" src="" style="margin:0;padding:0;max-height:100px;float:none;" />');
     });
     // Thanks: http://stackoverflow.com/questions/15281995/wordpress-create-category-ajax-response
     $(document).ajaxComplete(function(event, xhr, settings) {
       var queryStringArr = settings.data.split('&');
       if( $.inArray('action=add-tag', queryStringArr) !== -1 ){
         var xml = xhr.responseXML;
         $response = $(xml).find('term_id').text();
         if($response!=""){
           // Clear the thumb image
           $('#category-image-wrapper').html('');
         }
       }
     });
   });
 </script>
 <?php }

  }
 
$CT_TAX_META = new CT_TAX_META();
$CT_TAX_META -> init();
 
}

Breaking it down

We can go through this step by step. We’ve created a class to hold the code to make it easier to re-use but you can easily use this code in your functions.php file if you prefer. You’d just need to refactor it slightly.

Add new meta data term

Our first function adds a new field to the Add New Category form.

add-category-form

Note that we’re storing the attachment ID for the image in a hidden input field then displaying the thumbnail to the user. We’re also adding two buttons, the JavaScript for which we’ll add later.

public function add_category_image ( $taxonomy ) { ?>
   <div class="form-field term-group">
     <label for="category-image-id"><?php _e('Image', 'hero-theme'); ?></label>
     <input type="hidden" id="category-image-id" name="category-image-id" class="custom_media_url" value="">
     <div id="category-image-wrapper"></div>
     <p>
       <input type="button" class="button button-secondary ct_tax_media_button" id="ct_tax_media_button" name="ct_tax_media_button" value="<?php _e( 'Add Image', 'hero-theme' ); ?>" />
       <input type="button" class="button button-secondary ct_tax_media_remove" id="ct_tax_media_remove" name="ct_tax_media_remove" value="<?php _e( 'Remove Image', 'hero-theme' ); ?>" />
    </p>
   </div>
 <?php
 }

We add this via a hook which we call in our init function:

add_action( 'category_add_form_fields', array ( $this, 'add_category_image' ), 10, 2 );

If you wanted to add this field to a different taxonomy, e.g. for a custom post type, you’d need to replace the reference to category with a reference to your own taxonomy slug. For example, if you add created a genre taxonomy you would hook this function via add_action( 'taxonomy_add_form_fields', array ( $this, 'add_category_image' ), 10, 2 ).

If you’ve just added this and nothing else, you’ll see the buttons appear in your form but they won’t yet work. To get them to function as we need, we add some inline JavaScript to the footer via the admin_footer hook:

add_action( 'admin_footer', array ( $this, 'add_script' ) );

Then the function add_script:

/*
 * Add script
 * @since 1.0.0
 */
 public function add_script() { ?>
   <script>
     jQuery(document).ready( function($) {
       function ct_media_upload(button_class) {
         var _custom_media = true,
         _orig_send_attachment = wp.media.editor.send.attachment;
         $('body').on('click', button_class, function(e) {
           var button_id = '#'+$(this).attr('id');
           var send_attachment_bkp = wp.media.editor.send.attachment;
           var button = $(button_id);
           _custom_media = true;
           wp.media.editor.send.attachment = function(props, attachment){
             if ( _custom_media ) {
               $('#category-image-id').val(attachment.id);
               $('#category-image-wrapper').html('<img class="custom_media_image" src="" style="margin:0;padding:0;max-height:100px;float:none;" />');
               $('#category-image-wrapper .custom_media_image').attr('src',attachment.sizes.thumbnail.url).css('display','block');
             } else {
               return _orig_send_attachment.apply( button_id, [props, attachment] );
             }
            }
         wp.media.editor.open(button);
         return false;
       });
     }
     ct_media_upload('.ct_tax_media_button.button'); 
     $('body').on('click','.ct_tax_media_remove',function(){
       $('#category-image-id').val('');
       $('#category-image-wrapper').html('<img class="custom_media_image" src="" style="margin:0;padding:0;max-height:100px;float:none;" />');
     });
     // Thanks: http://stackoverflow.com/questions/15281995/wordpress-create-category-ajax-response
     $(document).ajaxComplete(function(event, xhr, settings) {
       var queryStringArr = settings.data.split('&');
       if( $.inArray('action=add-tag', queryStringArr) !== -1 ){
         var xml = xhr.responseXML;
         $response = $(xml).find('term_id').text();
         if($response!=""){
           // Clear the thumb image
           $('#category-image-wrapper').html('');
         }
       }
     });
   });
 </script>
 <?php }

Now, when clicking the Add Image button the WordPress media uploader will launch and allow you to select an image. It’ll grab the ID of the selected image and insert that into the hidden field with the ID category-image-id. This is the field that we’ll actually save. In order to present the image to the user, we use some jQuery to populate the div with the ID category-image-wrapper with the thumbnail image. This isn’t strictly necessary but just makes for a better user experience.

Likewise, if the user clicks the Remove Button image, the hidden field will be cleared and the image removed.

Save the meta data

Next, we need to be able to save our image meta field when the user clicks Add New Category. To do this, we hook into the created_category hook. If you’re working with a different taxonomy, then you’ll need to work with the created_{$taxonomy} hook where {$taxonomy} is your custom taxonomy slug. So our hook is:

add_action( 'created_category', array ( $this, 'save_category_image' ), 10, 2 );

And our function is:

public function save_category_image ( $term_id, $tt_id ) {
   if( isset( $_POST['category-image-id'] ) && '' !== $_POST['category-image-id'] ){
     $image = $_POST['category-image-id'];
     add_term_meta( $term_id, 'category-image-id', $image, true );
   }
 }

This works exactly the same way as add_post_meta by saving the value of our category-image-id field (which contains the attachment ID) to the category ID.

Update the meta data

Now that you’ve save a category with its associated image, you might wish to change the image.

edit-category-form

First, we’ll need to add the same fields to the Edit Category form that we added to the Add New Category form. The hook we use is category_edit_form_fields – replace category with your taxonomy slug if needed.

add_action( 'category_edit_form_fields', array ( $this, 'update_category_image' ), 10, 2 );

The function adds our fields to the Edit Category form:

/*
  * Edit the form field
  * @since 1.0.0
 */
 public function update_category_image ( $term, $taxonomy ) { ?>
   <tr class="form-field term-group-wrap">
     <th scope="row">
       <label for="category-image-id"><?php _e( 'Image', 'hero-theme' ); ?></label>
     </th>
     <td>
       <?php $image_id = get_term_meta ( $term -> term_id, 'category-image-id', true ); ?>
       <input type="hidden" id="category-image-id" name="category-image-id" value="<?php echo $image_id; ?>">
       <div id="category-image-wrapper">
         <?php if ( $image_id ) { ?>
           <?php echo wp_get_attachment_image ( $image_id, 'thumbnail' ); ?>
         <?php } ?>
       </div>
       <p>
         <input type="button" class="button button-secondary ct_tax_media_button" id="ct_tax_media_button" name="ct_tax_media_button" value="<?php _e( 'Add Image', 'hero-theme' ); ?>" />
         <input type="button" class="button button-secondary ct_tax_media_remove" id="ct_tax_media_remove" name="ct_tax_media_remove" value="<?php _e( 'Remove Image', 'hero-theme' ); ?>" />
       </p>
     </td>
   </tr>
 <?php
 }

This will also use the JavaScript we added previously to allow us to upload or remove images.

To save the updated field, we hook into edited_category and use update_term_meta. As before, you can use edited_{$taxonomy} for your own taxonomy.

/*
 * Update the form field value
 * @since 1.0.0
 */
 public function updated_category_image ( $term_id, $tt_id ) {
   if( isset( $_POST['category-image-id'] ) && '' !== $_POST['category-image-id'] ){
     $image = $_POST['category-image-id'];
     update_term_meta ( $term_id, 'category-image-id', $image );
   } else {
     update_term_meta ( $term_id, 'category-image-id', '' );
   }
 }

Display the image on the front end

Now all we need is to display the image in our theme. Just use get_term_meta as you would get_post_meta for post data, e.g.:

// Get the current category ID, e.g. if we're on a category archive page
$category = get_category( get_query_var( 'cat' ) );
 $cat_id = $category->cat_ID;
// Get the image ID for the category
$image_id = get_term_meta ( $cat_id, 'category-image-id', true );
// Echo the image
echo wp_get_attachment_image ( $image_id, 'large' );

And that’s it. Don’t forget to check out the Hero Theme demo to see this in action.

townscapes


Do you develop plugins?

You should try Wisdom, a plugin designed specifically for plugin developers that allows you to track your plugin's usage and provides invaluable data on how your plugin is being used. Find out more.

18 thoughts on “Adding an image upload field to categories

i replaced “category” with custom tax name “cars”. it shows the buttons but script dont work, so i cant open the uploader to upload image.

Reply

Have you checked for a JavaScript error in the console?

Reply

yes it was an error and i solved it by this code

function load_wp_media_files() {
wp_enqueue_media();
}
add_action( ‘admin_enqueue_scripts’, ‘load_wp_media_files’ );
——
now when i duplicate code to other taxonomy i get an error with upload field and cant choose image for both taxonomies

may you check the final code there
https://stackoverflow.com/questions/35864803/conflict-in-taxonomy-image-fields

Reply

your final code

term_id, ‘writers-image-id’, true ); ?>

Reply

Okay, I think I see. It’s not designed for more than one taxonomy. It’s intended to allow you to add an image field to categories but it hasn’t been designed to work with multiple taxonomies. I would suggest that you don’t make the field IDs specific to the taxonomy. In this way, the JS won’t look for fields that aren’t there.

Reply

Works nice for custom taxonomy if you Initialize it for every custom taxonomy, something like this:
add_action( ‘my-tax_add_form_fields’, array ( $this, ‘add_category_image’ ), 10, 2 );
add_action( ‘created_my-tax’, array ( $this, ‘save_category_image’ ), 10, 2 );
add_action( ‘my-tax_edit_form_fields’, array ( $this, ‘update_category_image’ ), 10, 2 );
add_action( ‘edited_my-tax’, array ( $this, ‘updated_category_image’ ), 10, 2 );

where my-tax is the name of the taxonomy.

Thank you!

Reply

Image not displaying in frontend ?

// Get the current category ID, e.g. if we’re on a category archive page
$category = get_category( get_query_var( ‘English’ ) );
$cat_id = $category->cat_ID;
// Get the image ID for the category
$image_id = get_term_meta ( $cat_id, ‘category-image-id’, true );
// Echo the image
echo wp_get_attachment_image ( $image_id, ‘large’ );

Reply

Tricky to help without seeing all the code but a couple of things perhaps to check:

On the first line, are you passing the correct key to get_query_var? I’d expect the key to be lower case for one thing.

I’d try printing the value of each variable line by line to see where the code fails. That will give you something to work on.

And, is there definitely an image uploaded to that category?

Reply

Just by pasting in “the Whole Thing” code, into the functions file, this does not work. I also get a js error within the console.

TypeError: wp.media is undefined[Learn More]

Any help?

Reply

Hi Guys, great article. Seem to be having a similar issue to Mohamed above. Adding my custom taxonomy put I am getting a js error in the console.

Uncaught TypeError: Cannot read property ‘editor’ of undefined relating to _orig_send_attachment = wp.media.editor.send.attachment;

I have added

function load_wp_media_files() {
wp_enqueue_media();
}
add_action( ‘admin_enqueue_scripts’, ‘load_wp_media_files’ );

but I still have the same issues. Any ideas on how to get round this?

Reply

So inserting an image into post throws a JS error in the console and the image is not displayed until the update button is hit.

the problem seems to be here:

wp.media.editor.send.attachment = function (props, attachment) {
if (_custom_media) {
$(‘#category-image-id’).val(attachment.id);
$(‘#category-image-wrapper’).html(”);
$(‘#category-image-wrapper .custom_media_image’).attr(‘src’, attachment.sizes.thumbnail.url).css(‘display’, ‘block’);
} else {
return _orig_send_attachment.apply(button_id, [props, attachment]);
}
}

Reply

So it seems that when adding an image. Before adding a thumbnail above the add/remove the JS script looks for an existing image to modify its src to that of the new one, only it finds nothing and calling .url on nothing returns the undefined error.
trying to fix.

Reply

Very useful! Thanks for sharing.

The only thing I ran into when adding the class to my project was that the WP media uploader was not enqueued on the edit-category page.
So I added this function:

public function is_editing_category() {
$screen = get_current_screen();
if($screen->id === ‘edit-category’)
wp_enqueue_media();
}

and hooked it to the ‘current_screen’ action:
add_action( ‘current_screen’, array ( $this, ‘is_editing_category’ ), 10, 2 );

Reply

The code works like charm! But I get a console js error as soon as I upload an image, “Uncaught TypeError: Cannot read property ‘url’ of undefined”. I hope you’ll help!

Reply

Hi smarica

This probably has to do with the line:

$(‘#category-image-wrapper .custom_media_image’).attr(‘src’,attachment.sizes.thumbnail.url).css(‘display’,’block’);

My guess is that your image doesn’t have a thumbnail size registered for some reason. You could try outputting attachment.sizes in the console to check what was going on.

Gareth

Reply

Hi gareth, thank you for the reply.. i got next issue as well.. Uncaught TypeError: Cannot read property value of undefined”.. plz help me with this

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *