In our Hero theme, we wanted to use large images as much as possible – including at the top of category pages. The theme is integrated with WooCommerce which already allows you to add an image to your product categories but we wanted to extend this to standard blog categories as well.

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

Published by Catapult Themes

I make themes and plugins for WordPress

17 Comments

  1. 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
  2. Have you checked for a JavaScript error in the console?

    Reply
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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 *