Menu Search
Jump to the content X X
Smashing Conf New York

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Introducing Term Meta Data In WordPress And How To Use Them

WordPress 4.4 introduced term meta data which allows you to save meta values for terms in a similar way to post meta data. This is a highly anticipated and logical addition to the WordPress system.

So far, the post and comment meta systems allowed us to add arbitrary data to posts and comments. This can be used to add ratings to comments, indicate your mood while you were writing a post, attach prices to product posts, and various other information you think is relevant to your content. As of the newest version of WordPress, meta data can now be added to terms which allows us to create features like default category thumbnails in a standardized way. This tutorial will show you how you can edit, update and retrieve these meta data for terms.

Under The Hood Of Term Meta Data Link

The logic behind term meta data is not new, already being used for posts, comments and users.

To enable meta data for terms, the new termmeta table was introduced. It saves the combination of the term_id, meta_key and meta_value, plus an incrementing meta_id.

Meta Data Functions Link

Four new functions were introduced to handle create, read, update and delete (CRUD) operations for term meta data:

  • add_term_meta(): adds the meta data
  • update_term_meta(): updates existing meta data
  • delete_term_meta(): deletes meta data
  • get_term_meta(): retrieves the meta data

Under the hood, these functions use the same code that the corresponding functions for post meta data use as well.

How To Use Term Meta Data Link

In a recent project, I had to assign additional attributes to non-hierarchical terms, which was a great chance to test the new meta data feature.

The project used a custom post type to represent houses. The features of a house (e.g. TV, sofa, etc.) were terms in a custom taxonomy. The editor of the house information wanted to list the features based on a group that should not be a feature by itself. Hence, I decided to add this group using term meta data.

The taxonomy I used was house_feature, but you can also use the existing category or post_tag taxonomies if you want to use term meta data with post categories or tags.

First, I created my feature taxonomy:

add_action('init', 'register_feature_taxonomy');

function register_feature_taxonomy() {
    $labels = array(
        'name' => _x( 'Features', 'taxonomy general name', 'my_plugin' ),
        'singular_name' => _x('Features', 'taxonomy singular name', 'my_plugin'),
        'search_items' => __('Search Feature', 'my_plugin'),
        'popular_items' => __('Common Features', 'my_plugin'),
        'all_items' => __('All Features', 'my_plugin'),
        'edit_item' => __('Edit Feature', 'my_plugin'),
        'update_item' => __('Update Feature', 'my_plugin'),
        'add_new_item' => __('Add new Feature', 'my_plugin'),
        'new_item_name' => __('New Feature:', 'my_plugin'),
        'add_or_remove_items' => __('Remove Feature', 'my_plugin'),
        'choose_from_most_used' => __('Choose from common Feature', 'my_plugin'),
        'not_found' => __('No Feature found.', 'my_plugin'),
        'menu_name' => __('Features', 'my_plugin'),
    );

    $args = array(
        'hierarchical' => false,
        'labels' => $labels,
        'show_ui' => true,
    );

    register_taxonomy('house_feature', array('houses'), $args);
}

You should change the houses post type in the last line to the one you are using, or just to post if you want to take the new taxonomy out for a test run on default posts.

If you use the code from this article in your theme’s functions.php file or within a plugin, make sure that the text domain my_plugin matches the text domain of your theme or plugin. The my_plugin text domain would work in a plugin with a my_plugin slug.

The groups I assigned the house features to were managed through settings. It can also be a simple array. For the sake of a short, but useful introduction into term meta, I am using the following global array to hold the available groups:

$feature_groups = array(
    'bedroom' => __('Bedroom', 'my_plugin'),
    'living' => __('Living room', 'my_plugin'),
    'kitchen' => __('Kitchen', 'my_plugin')
);

Extending The Term Form Link

To assign meta data to terms, we need to extend the term edit form. The complicated part is that the hooks we need from now on are built dynamically.

Adding Meta Data With A New Term Link

In order to extend the form to add a term, we make use of the {$taxonomy}_add_form_fields hook. For our house_feature taxonomy it resolves into house_feature_add_form_fields.

add_action( 'house_feature_add_form_fields', 'add_feature_group_field', 10, 2 );
function add_feature_group_field($taxonomy) {
    global $feature_groups;
    ?><div class="form-field term-group">
        <label for="featuret-group"><?php _e('Feature Group', 'my_plugin'); ?></label>
        <select class="postform" id="equipment-group" name="feature-group">
            <option value="-1"><?php _e('none', 'my_plugin'); ?></option><?php foreach ($feature_groups as $_group_key => $_group) : ?>
                <option value="<?php echo $_group_key; ?>" class=""><?php echo $_group; ?></option>
            <?php endforeach; ?>
        </select>
    </div><?php
}

This adds a select form right between the original form fields and the submit button.

create term form with meta data field1
The form to create a new term in the custom taxonomy should now look like this. (View large version2)

To save the term meta we need to hook into the action, which is fired when the new term is being saved. This time we use the created_{$taxonomy} hook.

add_action( 'created_house_feature', 'save_feature_meta', 10, 2 );

function save_feature_meta( $term_id, $tt_id ){
    if( isset( $_POST['feature-group'] ) && '' !== $_POST['feature-group'] ){
        $group = sanitize_title( $_POST['feature-group'] );
        add_term_meta( $term_id, 'feature-group', $group, true );
    }
}

We get the term data from the $_POST array sent with the form and save it using the new add_term_meta() function. Like add_post_meta(), it takes four arguments:

  • $term_id: the id of the term,
  • $meta_key: the meta key,
  • $meta_value: the value,
  • $unique: whether the key can only be used once; defaults to false.

I am setting $unique to true, because I want each feature of the house to be listed in only one group.

Updating A Term With Meta Data Link

Even though they share similar features, adding a new term and updating an existing one is technically different in WordPress. Therefore, we need to add an update routine too.

We make use of the {$taxonomy}_edit_form_fields hook to get the field for the group into the edit form.

add_action( 'house_feature_edit_form_fields', 'edit_feature_group_field', 10, 2 );

function edit_feature_group_field( $term, $taxonomy ){
                
    global $feature_groups;
          
    // get current group
    $feature_group = get_term_meta( $term->term_id, 'feature-group', true );
                
    ?><tr class="form-field term-group-wrap">
        <th scope="row"><label for="feature-group"><?php _e( 'Feature Group', 'my_plugin' ); ?></label></th>
        <td><select class="postform" id="feature-group" name="feature-group">
            <option value="-1"><?php _e( 'none', 'my_plugin' ); ?></option>
            <?php foreach( $feature_groups as $_group_key => $_group ) : ?>
                <option value="<?php echo $_group_key; ?>" <?php selected( $feature_group, $_group_key ); ?>><?php echo $_group; ?></option>
            <?php endforeach; ?>
        </select></td>
    </tr><?php
}

In addition to the add form, we need to retrieve the existing term data using get_term_meta() and pre-select it. This function has three arguments:

  • $term_id: the id of the term,
  • $key: the key of the meta data,
  • $single: whether to return a single result; defaults to false which would return an array.

Since I am expecting only one value to be returned, I set $single to true.

We now need to hook into edited_{$taxonomy} to save the data.

add_action( 'edited_house_feature', 'update_feature_meta', 10, 2 );

function update_feature_meta( $term_id, $tt_id ){

    if( isset( $_POST['feature-group'] ) && '' !== $_POST['feature-group'] ){
        $group = sanitize_title( $_POST['feature-group'] );
        update_term_meta( $term_id, 'feature-group', $group );
    }
}

We use update_term_meta() to overwrite the existing value instead of adding a new set of meta data.

Displaying The Term Meta Data In The Term List Link

The meta data is saved now. Let’s display it in a new column in the term list table. First, let’s add the column and header to the table. Finding the correct hook to extend a table in the WordPress dashboard is again a bit tricky because of so many flexible hook names.

The pattern you can use here is manage_edit-{$taxonomy}_columns, even though this is not exactly how the hook is defined.

add_filter('manage_edit-house_feature_columns', 'add_feature_group_column' );

function add_feature_group_column( $columns ){
    $columns['feature_group'] = __( 'Group', 'my_plugin' );
    return $columns;
}

To add the content into the column fields, you can use the hook pattern manage_{$taxonomy}_custom_column.

add_filter('manage_house_feature_custom_column', 'add_feature_group_column_content', 10, 3 );

function add_feature_group_column_content( $content, $column_name, $term_id ){
    global $feature_groups;

    if( $column_name !== 'feature_group' ){
        return $content;
    }

    $term_id = absint( $term_id );
    $feature_group = get_term_meta( $term_id, 'feature-group', true );

    if( !empty( $feature_group ) ){
        $content .= esc_attr( $feature_groups[ $feature_group ] );
    }

    return $content;
}

Again, we use get_term_meta() to retrieve the value and then simply attach it to the existing content. This would allow other developers to attach something else here as well.

It was in the nature of my project, that many terms shared the same value, so let’s make the group column sortable. We only need to add it to the sortable columns.

add_filter( 'manage_edit-house_feature_sortable_columns', 'add_feature_group_column_sortable' );

function add_feature_group_column_sortable( $sortable ){
    $sortable[ 'feature_group' ] = 'feature_group';
    return $sortable;
}

This is how the term list might look like with an extra column for term data:

term list with meta data3
Term list with meta data table. (View large version4)

Deleting Term Meta Data Link

If you remove a term, the corresponding meta data will also be removed, so you don’t need to clean up after yourself.

However, if you find yourself needing to remove term meta data, you can use the delete_term_meta() function. It takes up to three arguments:

  • $term_id: the id of the term
  • $meta_key: the meta key
  • $meta_value: the previous value

You only need to provide $meta_value if you want to remove meta data with a specific value.

Retrieving Terms By Meta Value Link

Like with posts, you can also retrieve terms using the meta values. Set the meta_query parameter when using get_terms() or wp_get_object_terms().

With the following query I would retrieve all features in the kitchen group.

$args = array(
    'hide_empty' => false, // also retrieve terms which are not used yet
    'meta_query' => array(
        array(
           'key'       => 'feature-group',
           'value'     => 'kitchen',
           'compare'   => 'LIKE'
        )
    )
);
                
$terms = get_terms( 'house_feature', $args );

The syntax of meta_query is the same as in WP_Query5, which allows you to make use of different operators for compare, like NOT LIKE, EXISTS, or BETWEEN.

Since I use meta data to group my terms, getting the result back ordered by the meta value would be really helpful. However, unlike with WP_Query, you can’t use orderby => meta_value yet and must sort the results after you retrieve them.

To display our terms and meta data, we iterate through the results and make use of get_term_meta() again.

if ( ! empty( $terms ) && ! is_wp_error( $terms ) ){
    echo '<ul>';
    foreach ( $terms as $term ) {
        echo '<li>' . $term->name . ' (' . get_term_meta( $term->term_id, 'feature-group', true ) . ')' . '</li>';
    }
    echo '</ul>';
}

Conclusion Link

I know many projects that already save meta information for custom taxonomies. A lot of them are probably going to update and use the new meta data logic once WordPress 4.4 is widely used.

The last thing I am missing now in the taxonomy roadmap is to be able to set meta data for a specific term–object relationship. Let me know if you can relate to that in the comments.

Resources Link

Excerpt image: Nikolay Bachiyski8

(dp, jb, ml, og)

Footnotes Link

  1. 1 https://www.smashingmagazine.com/wp-content/uploads/2015/11/feature-field-create-form-opt.png
  2. 2 https://www.smashingmagazine.com/wp-content/uploads/2015/11/feature-field-create-form-opt.png
  3. 3 https://www.smashingmagazine.com/wp-content/uploads/2015/11/feature-list-with-meta-opt.png
  4. 4 https://www.smashingmagazine.com/wp-content/uploads/2015/11/feature-list-with-meta-opt.png
  5. 5 https://codex.wordpress.org/Class_Reference/WP_Query#Custom_Field_Parameters
  6. 6 https://codex.wordpress.org/Function_Reference/get_terms
  7. 7 https://make.wordpress.org/core/2015/10/23/4-4-taxonomy-roundup/
  8. 8 https://www.flickr.com/photos/nbachiyski/2536017020/
SmashingConf New York

Hold on, Tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? Like SmashingConf Barcelona, on October 25–26, with smart design patterns and front-end techniques.

↑ Back to top Tweet itShare on Facebook

Thomas fell in love with WordPress as a freelance developer in 2010 and now focuses on his ad management plugin Advanced Ads and website monetization for his client’s and his own project about word-games. Dare to challenge him in German Scrabble.

  1. 1

    Moustafa Saber

    December 9, 2015 3:54 pm

    Great tutorial.
    Thanks Thomas.

    2
  2. 2

    Hey guys, why don’t you use anonymous functions as a second argument of add_action() function? It’s not a new feature, it was introduced in PHP 5.3.

    There are several reasons to do it:
    – First of all, It makes the code more readable.
    – There’s no point to create a function that is used only once. And in most cases add_action() callbacks are used only once.
    – It helps to keep the global namespace clean (not to pollute it with global functions).

    2
  3. 5

    Michael Dozark

    December 9, 2015 9:46 pm

    Minor quibble that may stem from not knowing more about the project: on the surface, taxonomies don’t seem like the right way to go for listing the features of a house. Isn’t that better handled by post meta?

    1
  4. 6

    Thanks for the article! I have a question:
    Do you think term meta data is a possible (good) solution to implement slug translations / rewrites?

    0
  5. 8

    Hi Michael, for me, the features for houses are like tags for posts. Taxonomies also come with archive pages in the frontend and forms in the dashboard. If you don’t want or need that in a project, you can of course use post meta too.

    0
  6. 9

    Advanced Custom Fields Plugin makes adding these meta fields ridiculously simple. Can’t recommend it enough.

    -1
    • 10

      Hi Graham, I don’t doubt that. My tutorial is ment to show how the new term meta data api can be used by developers. It is more about the core functions than the actual use case I present here.

      2
  7. 11

    How would one go about implementing a workaround for `orderby => meta_value`?

    I’m trying to order some taxonomies by a set of custom term meta, but can’t seem to find a solution. In the meantime I’m looking at some sort of patch for core.

    0
  8. 14

    There’s a small problem with your code while using WooCommerce (and possibly other plugins). WooCommerce adds custom columns to the Product Categories taxonomy (a custom taxonomy).

    Your code will add the value of the term to both the custom column added by your case as well as the custom column added by WooCommerce for the product category image.

    As a workaround I extended the if statement where we add a value to the column if the termmeta is not empty:
    if( !empty( $feature_group ) && $column_name == ‘feature_group’ ){
    $content .= esc_attr( $feature_groups[ $feature_group ] );
    }

    So we check two things: that the meta is not empty and that we output the value ONLY if we are in the correct column.

    Otherwise, thanks for the tutorial.

    1
  9. 16

    im getting massive errors in what seems to be related to termmeta in regards to a table called gxfdtg_termmeta i seriously dont no how to fix and its slowing my page load time down if even loading it at

    [15-Dec-2015 02:09:33] WordPress database error Table ‘ldsgdg_dgagagag.gxfdtg_termmeta’ doesn’t exist for query SELECT term_id, meta_key, meta_value FROM gxfdtg_termmeta WHERE term_id IN (14) ORDER BY meta_id ASC made by require(‘wp-blog-header.php’), require_once(‘wp-includes/template-loader.php’), include(‘/themes/twentyfifteen/page.php’), get_template_part, locate_template, load_template, require(‘/themes/twentyfifteen/content-page.php’), the_content, apply_filters(‘the_content’), call_user_func_array, do_shortcode, preg_replace_callback, do_shortcode_tag, call_user_func, em_get_events_list_shortcode, EM_Events::output, EM_Event->output, EM_Event->get_categories, EM_Categories->__construct, get_the_terms, wp_get_object_terms, update_termmeta_cache, update_meta_cache

    0
    • 17

      Hi Ben, please make sure that you are using WordPress 4.4. Term meta was only introduced with that version.

      0
    • 18

      Please ignore my last statement. Without WP 4.4 you wouldn’t come this far. Seems like an error with your update, because the relevant table is missing. Please try to reinstall.

      0
  10. 19

    ISTANBUL-CATI.COM
    FIRMAMIZ CATI SEKTORUNDE 1985 YILINDAN BU ZAMANA KADAR FALIYET GOSTERMEKTEDIR.
    CATI YAPIMI TAMIR TADILAT DERE OLUK SEKTORUNDE KESINTISIZ HIZMET SUNAN FIRMAMIZ,
    SIZE VERDIGI HIZMETIN GURURUNU YASIYOR.
    KALITE ANLAYISINDAN ODUN VERMEYEN FIRMAMIZ,
    HIZMET VERDIGI KURUMLARA KOSULSUZ MUSTERI MEMNUNIYETI SAGLAMAYI AMAC EDINMISDIR.
    HER TURLU TAMIRAT VE TADILAT ISLERINIZI UZMAN EKIPLERIMIZ TARAFINDAN SAGLAM VE UYGUN FIYATA YAPIP TESLIM ETMEKTEYIZ.

    BUNYEMIZDE CATI TAMIR TADILAT BAKIM ONARIM ISLERI,
    AHSAP VE CELIK CATILAR,
    CARDAK KAMELYA VE PERGOLA TERAS AÇMA VEYA KAPAMA,
    KIREMIT CATILAR, SHINGLE CATILAR, MEMBRAN CATILAR, SANDVIC PANEL CATILAR,
    ONDULINE CATILAR VE TRAPEZ SAC CATILAR OLARAK HIZMET ETMEKTEYIZ.
    DERE OLUK VE INISLERE GELINCE, BAKIR CINKO GALVANIZ SAC HIZMETIMIZ VARDIR.
    EV TADILAT VE TAMIRAT ISLERINDE UZMAN KADROMUZLA HIZMET ETMEKTEYIZ.
    DUVAR BOLME, ALÇIPAN ISLERI, BOYA BADANA, LAMBIRI, LAMINAT PARKE,
    DEMIR DOGRAMA VE KAYNAK ISLERINDEDE HIZMETINIZDEYIZ,
    SERVISLERIMIZ: BEYKOZ, USKUDAR, KADIKOY, MALTEPE, KARTAL, PENDIK, TUZLA,
    SILE, CEKMEKOY, SANCAKTEPE, UMRANIYE, ATASEHIR, BEYLERBEYI CENGELKOY BOSTANCI RIVA
    BOLGELERINDE HIZMET ETMEKTEYIZ..

    -10
  11. 20

    Hemang Rindani

    January 13, 2016 12:37 pm

    WordPress is an effortless Content Management System that helps a developer to do anything as required. It comes with number of themes and plugins that can make digital dream a reality. The features extends from developing a basic site to highly dynamic websites in multiple languages with number of users and unlimited functionalities. Latest versions has made WordPress more user and SEO friendly. While many were of the view that WP lacks SEO tools, I feel it has very strong plugins capable of making it one of the best platforms. Inclusion on Meta data in user comments and other submissions promises to improve search engine visibility. Meta data also helps developer and users to classify data and make the best use of it. Using such methods and including other SEO related tools like Yoast allow WP user to have some of the best websites.

    -5
  12. 21

    Kevin McAloon

    January 18, 2016 4:36 pm

    Thanks for this. This might be the most helpful post I’ve read about the updated API. Regarding custom inputs in the term’s form, any luck integrating the media uploader here to allow a smooth way to upload images? I have taken a couple of looks around but can’t seem to find any articles dealing with this using the updated API.

    0
    • 22

      James Romanowski

      January 25, 2016 4:09 pm

      Hey Kevin, I’m currently integrating the image uploader on the term edit screen, into a plugin I’m working on. So far I can upload the image, just working on saving it. Here’s an article I found in the WordPress Codex about how to integrate this correctly. – http://themefoundation.com/wordpress-meta-boxes-guide/

      Go to section 9.2 Image Uploader.

      0
  13. 23

    Nice article! I found this very useful as I am working on my own plugin.

    I do have a question, how can you integrate the media manager in this? I would like to upload a logo using the media manager but I cannot seem to find anything that shows how to do this. Any insight would be very much appreciated.

    Thanks for the great article!

    0
  14. 24

    Adding the media manager was pretty easy. Using the link from James, http://themefoundation.com/wordpress-meta-boxes-guide/, I was able to implement it.

    A quick example:

    <label for="-button">
    <img style="width: auto; height: auto;" id="-img" src="">

    <input type="hidden" id="" name="" value="">
    <input type="button" id="-button" class="button channelLogoAdd" value="" />

    /**
    * Attaches the image uploader to the input field
    */
    jQuery(document).ready(function ($) {

    // Instantiates the variable that holds the media library frame.
    var meta_image_frame;

    // Add image event
    $('.form-field').on('click', '#-button.channelLogoAdd', function (e) {
    // Prevents the default action from occuring.
    e.preventDefault();
    // If the frame already exists, re-open it.
    if (meta_image_frame) {
    meta_image_frame.open();
    return;
    }
    // Sets up the media library frame
    meta_image_frame = wp.media.frames.meta_image_frame = wp.media({
    title: "",
    button: {text: ""},
    library: {type: 'image'}
    });
    // Runs when an image is selected.
    meta_image_frame.on('select', function () {
    // Grabs the attachment selection and creates a JSON representation of the model.
    var media_attachment = meta_image_frame.state().get('selection').first().toJSON();
    // Sends the attachment URL to our custom image input field.
    $('#-img').attr('src', media_attachment.url);

    // Change
    $('#-button').removeClass('channelLogoAdd');
    $('#-button').addClass('channelLogoRemove');
    $('#-button').val('');
    $('#').val(media_attachment.id);
    });
    // Opens the media library frame.
    meta_image_frame.open();
    });

    // Remove image event
    $('.form-field').on('click', '#-button.channelLogoRemove', function (e) {
    // Prevents the default action from occuring.
    e.preventDefault();

    $('#-img').attr('src', '');
    $('#').val('');

    $('#-button').removeClass('channelLogoRemove');
    $('#-button').addClass('channelLogoAdd');
    $('#-button').val('');
    });
    });

    0
  15. 25

    Nikos Giannopoulos

    February 16, 2016 8:19 am

    I have on question. Sortable does not seem to working as it is supposed too. get_terms support only a list of variable in order by field.

    1
  16. 26

    SUPER-helpful and very thorough! I learned a ton from reading this, and now I have my custom meta field working in my client’s project.

    Thanks so much!

    0
  17. 27

    Like Nikos Giannopoulos, I am also having trouble getting the sorting to work on the meta field on the admin page. By following and adapting the code in the article, I got it to look as though you can sort by the meta field, but clicking on the header seems to actually just sort by the term name, not the meta value.

    Does anyone know how to get sorting to work on the meta value, on the admin page?

    0
  18. 28

    Great Tutorial.
    I was hoping with this new feature adding things like the comments template to a taxonomy term would be feasible though it appears the comments template would need to be re-written to accommodate this. Or a custom solution which uses the term ID rather than the post ID.

    0
  19. 29

    It’s now not working:
    “$args = array(
    ‘hide_empty’ => false, // also retrieve terms which are not used yet
    ‘meta_query’ => array(
    array(
    ‘key’ => ‘feature-group’,
    ‘value’ => ‘kitchen’,
    ‘compare’ => ‘LIKE’
    )
    )
    );

    $terms = get_terms( ‘house_feature’, $args );”

    0

↑ Back to top