Authors List Shortcode in WordPress

I’m quite sure you’re familiar with the Shortcode API. If you’re not, it’s a simple set of functions used by theme and plugin developers to create certain macro codes that can be used throughout the post content. [ gallery ] is one example, meaning you’ll see [ gallery ] when editing the post content, but when actually viewing the post, it generates a full-blown picture gallery out of your uploaded files.

Today we’ll create our own shortcode that will generate a list of authors/contributors with their names, avatars, bios and social media links. We’ll first define a few custom fields to hold our social links in user profiles, then create the shortcode itself and make it generate some basic markup. Finally we’ll quickly go through styling the output and as a bonus I’ll show you how to order the output the way you might need to.

Custom User Fields

The default API and interface for working with users in WordPress is not only good enough for most common setups, but also flexible. This means that with a few actions and filters, you can add your own custom fields to the Edit Profile page and store them in the user meta for later use.

There are four actions that we’ll use to add new fields to the edit user form and to process the input when the form is submitted. These are:

  • show_user_profile runs at the end of the profile form when editing the current user’s profile, i.e. your own
  • edit_user_profile same as the above, but runs when editing a different user’s profile (admins only should be allowed to do this)
  • personal_options_update runs when the profile form is submitted (on your own profile)
  • edit_user_profile_update runs when form is submitted on a different user’s profile (for admins)

Adding the Fields

We’ll keep it quite simple in our tutorial and we’ll use one callback function per pair. Let’s start with the first one to add the fields to the profile screen:

add_action( 'show_user_profile', 'my_user_profile_fields' );
add_action( 'edit_user_profile', 'my_user_profile_fields' );

And the callback function itself would print some extra fields in simple HTML and a table layout. We do miss the Settings API here, but okay:

function my_user_profile_fields( $user ) {
    <table class="form-table">
            <th><label for="twitter_url">Twitter URL</label></th>
                <input type="text" name="twitter_url" /><br />

I added only one field to hold the Twitter URL for brevity but you can go ahead and add one for Facebook, Linked In and Google+. Just make sure you don’t user reserved and too generic meta names. To be on the safe side you can even prefix them with your theme or plugin name.

This will result in the extra section created in the edit profile page, but as it is, it won’t work. The Twitter field will remain empty, that is because you’re not catching the input and WordPress won’t do it for you automatically (which is a good thing).

Catching and Saving the Input

Back to the actions I wrote about earlier, we’ll now hook to the update ones and have them read our POST data and save the field (or fields if you added more than one) into our user meta. So register the hooks:

add_action( 'personal_options_update', 'save_my_user_profile_fields' );
add_action( 'edit_user_profile_update', 'save_my_user_profile_fields' );

And write the callback function (again, one function for both actions):

function save_my_user_profile_fields( $user_id ) {
    if ( ! current_user_can( 'edit_user', $user_id ) )
        return false;

    update_user_meta( $user_id, 'twitter_url', $_POST['twitter_url'] );
    // Update your own fields here

First of all we run a quick check to see if the current user can really edit the user’s fields. Next, with one or more calls to the update_user_meta function we write our values into the database and that’s pretty much all there is to it. Here’s where we’re at:

Contributors Edit Profile

Now you’ve got some extra fields in your edit profile screen. So what? Well first of all you can use the meta anywhere in your template files now when dealing with the post author like a signature at the end of posts or perhaps on the author archives pages, but we’re working on a shortcode to list them all, remember?

Creating the Shortcode

If this is your first time creating a shortcode in WordPress, I suggest you read the Shortcode API Codex entry first. Our new shortcode won’t have any attributes and won’t have any content. We’ll call it authors-list so you’ll use [ authors-list ] in your post content to generate the output.

One quick note, throughout the article I’m writing the shortcodes with extra spaces. This way WordPress doesn’t recognize it as a shortcode so it gets printed as is instead of being parsed. To actually use the shortcodes you have to remove the extra space symbols.

add_shortcode('authors-list', 'my_authors_list_shortcode');
function my_authors_list_shortcode( $atts = array() ) {
    global $wpdb;
    $users = $wpdb->get_results( "SELECT ID, display_name FROM {$wpdb->users}" );

    $content = "<ul class='authors-list'>";
    foreach( $users as $user ) {
        $content .= "<li>";
        $content .= get_avatar( $user->ID, 70 );
        $content .= "<h3>" . $user->display_name . "</h3>";
        $content .= "<p class='author-description'>" . get_user_meta( $user->ID, 'description', true ) . "</p>";
        $content .= "<p><a href='". get_user_meta( $user->ID, 'twitter_url', true ) . "' class='twitter-icon' target='_blank'>Twitter</a><!-- add more here --></p>";
        $content .= "</li>";
    $content .= "</ul>";
    return $content;

Quote lengthy, eh? Let’s go through step by step. First we add a shortcode called authors-list with the add_shortcode function using the my_authors_list_shortcode as the callback function. Inside the callback function we’re accepting an $atts array but we’re not actually using it, although you might extend the shortcode to make use of some extra attributes later on.

Next we fire an SQL query to the global $wpdb object which is our WordPress database. We ask for all the users IDs and their display names, then loop through them after opening an unordered list. Note that all the output happens through the $content variable, not echo or print. This is because shortcodes have to return the values, not print them.

Inside the users loop we create a list item containing the user avatar, the display name, bio (user description) and the Twitter URL as a link. I gave the Twitter URL anchor the twitter-icon class so we can use some CSS to style it and give it a nice-looking Twitter icon later on. Here’s what we have so far:

Contributors Shortcode in Action

You can add more conditionals to make sure the Twitter URL actually exists before print it out as a link, same with the author description. I left it out for brevity, assuming that all users will have bios and Twitter profiles ;) Let’s move on to some styling.

Styling the Output

This is the fun part. You can go ahead and inspect what has been generated by the shortcode using Chrome’s developer tools or Firebug. You can add more classes to the shortcode itself if you need more control, but the basics are these:

ul.authors-list {
    list-style: none;
    margin: 0;
    padding: 0;

ul.authors-list li {
    margin-bottom: 30px;
    clear: both;

ul.authors-list img.avatar {
    float: left;

ul.authors-list p,
ul.authors-list h3 {
    display: block;
    margin-left: 80px;
    margin-bottom: 0;

ul.authors-list a.twitter-icon {
    background: url(twitter_icon.gif) 0 0 no-repeat;
    display: inline-block;
    width: 30px;
    height: 20px;
    text-indent: -3000px;

Note how I replaced the Twitter text in the anchor with a Twitter bird image. With a little more fine tuning to the styles above here’s what I have been able to achieve:

Authors Shortcode

But that’s not all.

Sorting/Ordering and Filtering

It’s cool if you’re running a blog with 5 contributing users and the shortcode will output them all ordered by their IDs. Sometimes you’d want to order by their names, sometimes you’d want a custom order and what if you’ve got 20 users 5 of which actually write the content? So you’d want to filter them out too.

For the best control you’d want a custom order. Of course a drag-n-drop interface would be cool and awesome, perhaps when you’re writing a neat plugin out of this. I like to keep thing as simple as possible, so what I did was create an extra meta field called Order and gave it a number. I filtered out all the users who’s order number is 0 or empty and ordered the rest by the numeric.

The filtering trick is quite straightforward, all you have to do is create an extra condition in the my_authors_list_shortcode function, while ordering is a tiny bit more complex. Suppose your ordering meta field is stored as my_order, here’s how you would replace the SQL query:

$users = $wpdb->get_results("SELECT ID, display_name FROM {$wpdb->users} JOIN
    {$wpdb->usermeta} ON {$wpdb->usermeta}.user_ID = {$wpdb->users}.ID WHERE
    {$wpdb->usermeta}.meta_key = 'my_order' ORDER BY CAST({$wpdb->usermeta}.meta_value AS  DECIMAL);");

Monsterous, eh? Well it’s quite simple actually, it just JOINs on the user meta table and filters out with the my_order meta key, then CASTs the value as a decimal (meta values are stored as character strings) and then finally hands it over to the ORDER BY clause.

If you’re dealing with thousands of users and millions of page visits, you might want to add an extra index on the ( user_id, meta_key ) fields and of course move any filtering conditions from the shortcode function into the SQL query too. This way you won’t be querying for all your thousands of users just to output three of them. Also check your slow query log and if needed tune your MySQL query cache (or maybe as a Transient) to cache the whole result of the query.


This is the same (or very close to the) technique we use on our about page. Of course most things come with a little bit of trial and error, but once you get it right you’ll understand exactly how this works. Watch out for privileges too, by adding extra fields to the profile screen all your users (including subscribers) will be able to see them and edit them for their profile, so you might want to introduce a few extra current_user_can calls before going into a live environment.

Hope you learned something new today and let us know if you have any questions or suggestions. Use the comment form below to share your thoughts and don’t forget that we’re tweeting about WordPress too ;) Enjoy your day!