How to Create an Alphabetical Index of Posts in WordPress

About a year ago, I was handed a design comp from a designer that required me to build out an alphabetical index for a custom post type in WordPress. You know, something like this:

WP Alpha Index

…where clicking on one of those letters would take you to a page showing only posts with titles starting with that letter. I managed to cobble together a solution that works pretty well. Not great, but passable. It involved writing quite a few custom functions – one to generate that bunch of links you see in that image above, one to register a query parameter so I could use it in WordPress, and then a few more functions to do the right queries in the database, a bunch of code in the template to make sure I was showing the right posts…and on and on.

I remember spending quite a bit of time hacking that all together and being pretty pleased with the results at the time. And then I promptly forgot about that ball of kludgy code…until that same designer handed me a design comp for another WordPress project just a couple months ago:

AZ Index WP

…and my first thought was, “Oh hey, I know how to do that. I’ll just open up that project from last year, and copy … OH SWEET MOTHER OF CHEESE WHAT WAS I THINKING?” Yeah. As I started hunting for and finding the bits of code scattered throughout the theme and plugins I’d written to accomplish that last time, I realized that there had to be a better way to go about this.

I was right. You can do this with a lot less code.

I was lucky to come across this post from Kathy is Awesome which set me on a much, much better path, though I quickly realized this bit of code was missing a few important things I needed. But with this little push in the right direction, I was off and running. And here’s how you can accomplish this bit of WP magic:

1. Plan a new plugin

This new plugin is going to do three things:

  1. Register a custom post type (I’m working with songs and song titles for my example)
  2. Register a custom taxonomy
  3. Use the ‘save_post’ hook to automagically assign new posts to the right taxonomy

If you’re going to be creating an alphabetical index of regular old blog posts, then you can skip that first step.

And, in case you’re wondering, yes, you could do all three of these things in your theme’s functions.php. But chances are that you’d like to have your custom post type, custom taxonomy, and alphabetical index available even if you decide to change themes or re-design your site, so a plugin is the better way to go.

2. Create a plugin

Go to /wp-content/plugins and create a new folder. I’m going to call mine alphaindex. Then create a blank index.php inside that folder.

Now, inside of index.php, we’ll add our header comment that tells WordPress about our plugin:

3. Register a custom post type

Next up, we’ll create a custom post type. I’m working with songs for my example, so I’ll create a post type called ‘song’.

This is a pretty common WordPress thing, so I won’t go into detail about creating a custom post type here. If you want to learn more about working with custom post types, check out the Codex.

4. Register a custom taxonomy

Now that we have a custom post type, we’ll create a custom taxonomy for that post type. I’m going to call mine ‘alpha’:

Again, since this is a pretty common WordPress thing, I won’t go into detail about creating custom taxonomies – there’s the Codex if you want to learn more. But I do want to point out a couple of important things about the way this taxonomy is set up.

First, be sure to set 'hierarchical' to 'false'. We don’t want to work with a hierarchical taxonomy here. Next, by setting 'show_ui' to false, we’re hiding the taxonomy from the WordPress admin. That allows our alphabetical index to appear to work automagically – you can set that to true if you want to see the taxonomy in the WordPress admin for troubleshooting or to assure that it’s working as expecting while you’re coding it up.

Also, yes, if you wanted to, you could register the custom post type and and the custom taxonomy inside the same function since they both get called on the 'init' hook. It works fine either way – just personal preference.

5. Automagically assign the correct taxonomy to the post when it’s saved

We’ll use the save_post hook. Here’s the function:

I’ve included comments throughout to explain what we’re doing step-by-step. Now, each time we save a post, the correct alphabetical taxonomy gets assigned.

6. Link to letters

Now that we’ve used a taxonomy for the letters of the alphabet, we’re just working with a taxonomy. To display a list of links to each letter, we just use wp_list_categories in the theme templates wherever we want them to display:

If you want to set up a special layout for these pages, create a taxonomy-alpha.php template in your theme.

7. Sort the posts alphabetically

Since we’re sorting our posts by alphabet letters, it makes the most sense to also sort them alphabetically. We’ll use the pre_get_posts hook. Use this function:

Now anytime we view the post archive for our custom post type or an archive page for our alpha taxonomy, the posts will be sorted alphabetically.


Now, next year if I need to do this again, hopefully I won’t find my code quite as cringeworthy. I especially like that we’re using the taxonomy functionality that’s already a part of WordPress – it makes it easier to work with and makes for nicer URLs too. Yay for no query parameters being required (as long as you’re using pretty permalinks of course).

In my case, I was creating this alphabetical index on a new site that didn’t have any content yet. What if you’re adding an alphabetical index to a site that already has dozens or hundreds of posts? That original post I referenced over at Kathy is Awesome shows you how to go through any existing posts and assign them to the correct taxonomy.


Image credit: Kristin Nador

 

21 thoughts on “How to Create an Alphabetical Index of Posts in WordPress

  1. by the way, there is a bit extra that I added so that you can display all of the alpha indexes even if there are no posts with the taxonomy, but only link through to the ones with the taxonomy:

    in functions/plugin:

    function set_alpha_default_terms(){

    // see if we already have populated any terms
    $alpha = get_terms( ‘alpha’, array( ‘hide_empty’ => false ) );

    // if no terms then lets add our terms
    //if( empty( $alpha ) ){
    $alphas = get_default_alpha();
    foreach( $alphas as $alpha ){
    if( !term_exists( $alpha[‘name’], ‘alpha’ ) ){
    wp_insert_term( $alpha[‘name’], ‘alpha’, array() );
    }
    }
    //}

    }
    function get_default_alpha(){

    $alpha = array(
    ‘0’ => array( ‘name’ => ‘0-9’),
    ‘1’ => array( ‘name’ => ‘a’ ),
    ‘2’ => array( ‘name’ => ‘b’ ),
    ‘3’ => array( ‘name’ => ‘c’ ),
    ‘4’ => array( ‘name’ => ‘d’ ),
    ‘5’ => array( ‘name’ => ‘e’ ),
    ‘6’ => array( ‘name’ => ‘f’ ),
    ‘7’ => array( ‘name’ => ‘g’ ),
    ‘8’ => array( ‘name’ => ‘h’ ),
    ‘9’ => array( ‘name’ => ‘i’ ),
    ’10’ => array( ‘name’ => ‘j’ ),
    ’11’ => array( ‘name’ => ‘k’ ),
    ’12’ => array( ‘name’ => ‘l’ ),
    ’13’ => array( ‘name’ => ‘m’ ),
    ’14’ => array( ‘name’ => ‘n’ ),
    ’15’ => array( ‘name’ => ‘o’ ),
    ’16’ => array( ‘name’ => ‘p’ ),
    ’17’ => array( ‘name’ => ‘q’ ),
    ’18’ => array( ‘name’ => ‘r’ ),
    ’19’ => array( ‘name’ => ‘s’ ),
    ’20’ => array( ‘name’ => ‘t’ ),
    ’21’ => array( ‘name’ => ‘u’ ),
    ’22’ => array( ‘name’ => ‘v’ ),
    ’23’ => array( ‘name’ => ‘w’ ),
    ’24’ => array( ‘name’ => ‘x’ ),
    ’25’ => array( ‘name’ => ‘y’ ),
    ’26’ => array( ‘name’ => ‘z’ ),
    );

    return $alpha;
    }

    and for index:

    $terms = get_terms(‘alpha’, array(‘hide_empty’ => ‘0’));
    $used_terms = get_terms(‘alpha’, array(‘hide_empty’ => ‘1’));

    //$str = str_replace_once(”, ”, $str);
    if ( !empty( $terms ) && !is_wp_error( $terms ) ){
    echo “”;
    foreach ( $terms as $term ) {
    ?>

    <?php echo in_array($term,$used_terms)?" “:””;?>
    name ?>
    <?php echo in_array($term,$used_terms)?"
    “:””;?>

    <?php
    }
    echo "”;
    }

    I also added an ‘archive page’ that is actually just a rewrite of your chosen url to the first alpha index that has posts.

    in functions/plugin I changed the custom taxonomy to use the same permalink structure as the post it was attached to.

    function alphaindex_alpha_tax() {
    register_taxonomy( ‘alpha’,array (
    0 => ‘vender’,
    ),
    array( ‘hierarchical’ => false,
    ‘label’ => ‘Alpha’,
    ‘show_ui’ => false,
    ‘query_var’ => true,
    ‘show_admin_column’ => false,
    ‘rewrite’ => array(‘slug’ => ‘venders’)
    ) );
    }

    I then added the url rewrite for the ‘archive’:

    function add_alpha_rewrite_rules($rules)
    {
    $terms = get_terms(‘alpha’);

    $new_rules = array(
    ‘venders($)’ => ‘index.php?taxonomy=alpha&term=’.$terms[0]->slug.’/’
    );

    return $new_rules + $rules; // add the rules on top of the array
    }
    add_action( ‘rewrite_rules_array’, ‘add_alpha_rewrite_rules’ );

    and I also a line at the bottom of the save function so that anytime a post if saved with the taxonomy the archive permalink is adjusted:

    function alphaindex_save_alpha( $post_id ) {
    if ( defined( ‘DOING_AUTOSAVE’ ) && DOING_AUTOSAVE )
    return;
    //only run for songs
    $slug = ‘vender’;
    $letter = ”;
    // If this isn’t a ‘song’ post, don’t update it.
    if ( isset( $_POST[‘post_type’] ) && ( $slug != $_POST[‘post_type’] ) )
    return;
    // Check permissions
    if ( !current_user_can( ‘edit_post’, $post_id ) )
    return;
    // OK, we’re authenticated: we need to find and save the data
    $taxonomy = ‘alpha’;
    if ( isset( $_POST[‘post_type’] ) ) {
    // Get the title of the post
    $title = strtolower( $_POST[‘post_title’] );

    // The next few lines remove A, An, or The from the start of the title
    $splitTitle = explode(” “, $title);
    //$blacklist = array(“an”,”a”,”the”);
    $blacklist = array();
    $splitTitle[0] = str_replace($blacklist,””,strtolower($splitTitle[0]));
    $title = implode(” “, $splitTitle);

    // Get the first letter of the title
    $letter = substr( $title, 0, 1 );

    // Set to 0-9 if it’s a number
    if ( is_numeric( $letter ) ) {
    $letter = ‘0-9’;
    }
    }
    //set term as first letter of post title, lower case
    wp_set_post_terms( $post_id, $letter, $taxonomy );
    flush_rewrite_rules();
    }

    Sorry about the formatting that is no doubt off. I hope someone finds this all useful! 🙂

  2. Hey Nathalie,

    Thanks for this – just what i was looking for for the film database I’m making. I have one question though, hope you don’t mind.

    My site is in Spanish so instead of removing “a”, “an” and “the”, I’m removing “el”, “la”, “un” and “una”. For some reason though the wp_list_categories output is simply ignoring the films that start with any of those words – do you know why that might be? Is there something else I should change in the code somewhere?

    Many thanks

    1. Maybe it’s my code that’s throwing up a similar issue, but I think it’s down to the fact this line:

      (line 27)
      $letter = substr( $title, 0, 1 );

      Isn’t dealing with titles that have the blacklisted words removed.

      To combat this I amended line 24:

      $title = implode(“”, $splitTitle);

    2. Semantically it’s probably best to amend this line:

      $splitTitle[0] = str_replace($blacklist, “”, strtolower($splitTitle[0]));

      I’d remove the array value rather than string replace.

  3. Hi, I am just a baby geek but really need an az index. did you make a wordpress plugin for this that is downloadable like other plugins? You know, so I don’t have to code?
    thanks in advance.
    Billie

  4. Thanks for this great post, it really like your method of using a taxonomy to create an alphabetical index.

    I found one issue – post titles that started with A or The, such as ‘A blah blah blah’ or ‘The blah blah blah’, were being given a blank category. I modified the alphaindex_save_alpha function as follows to fix this:

    <?php

    function alphaindex_save_alpha( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
    return;

    //only run for specific post type
    $slug = 'programs_services';
    $letter = '';

    // If this isn't the specified post type, don't update it.
    if ( isset( $_POST['post_type'] ) && ( $slug != $_POST['post_type'] ) )
    return;

    // Check permissions
    if ( !current_user_can( 'edit_post', $post_id ) )
    return;

    // OK, we're authenticated: we need to find and save the data
    $taxonomy = 'alpha';

    if ( isset( $_POST['post_type'] ) ) {

    // Get the title of the post
    $title = strtolower( $_POST['post_title'] );

    // The next few lines remove A, An, or The from the start of the title
    $blacklist = array('a', 'an', 'the');

    // Make a pattern to ensure to only remove blacklisted items that are followed by a space (so not to remove works like 'theme')
    $pattern = '/\b(?:' . join('|', $blacklist) . ')\b/i';

    // Remove the blacklisted words if they exist
    $title = preg_replace($pattern, '', $title);

    // Remove any spaces from the beginning of the title
    $title = ltrim($title);

    // Get the first letter of the title
    $letter = substr( $title, 0, 1 );

    // Set to 0-9 if it's a number
    if ( is_numeric( $letter ) ) {
    $letter = '0-9';
    }
    }

    //set term as first letter of post title, lower case
    wp_set_post_terms( $post_id, $letter, $taxonomy );

    }
    add_action( 'save_post', 'alphaindex_save_alpha' );

  5. Im totally lost.

    The step 2 i created a plugin folder and index.php.

    Steps 3, 4 and 5 i inserted the codes into theme´s functions.php.

    Step 6 inserted the code into theme´s index.php.

    Wheres the step 7´s code?

    And why the plugin´s index.php (step 2) is incompleted?

    1. The code for all steps except for step 6 should go into the plugin file that you created. The code for step 6 will go into your theme where you’d like to display the alphabetical post links. It looks like all of the code is there for all 7 steps when I view the page, so maybe it was just a temporary glitch with GitHub’s gist hosting?

      Step 2 is just showing how to do the basic setup of a plugin. The steps that follow are the rest of the code for the plugin.

  6. Hi, Thanks for your precious article, I have a big problem to solve.
    In my site there are different type of archive.php templates and the archive.php is not the right one.
    I need to redirect the plug in to a specific archive.php template when a letter is clicked. How can I do that?

    Thank you so much

  7. First of all thank you for this! Everything works but the styling of #6. I’ve tried styling it with CSS but it seems to be ignoring the style sheet altogether. I want the pagination to show horizontally and without bullets – pretty much like how you have it illustrated in your first paragraph.

    How did you do that?

  8. hello, lovely work but how can ı use this for custom taxanomy ? ı mean ı have already a taxanomy and ı wanna alphabeticaly listing that taxonomy.

  9. Is there a plugin now where I don’t have to create my own plugin to be able to alphabetize my posts? In other words just a simple click and activate…Would like an A-Z menu on my site.

  10. by the way, there is a bit extra that I added so that you can display all of the alpha indexes even if there are no posts with the taxonomy, but only link through to the ones with the taxonomy:

    in functions/plugin:

    function set_alpha_default_terms(){

    // see if we already have populated any terms
    $alpha = get_terms( ‘alpha’, array( ‘hide_empty’ => false ) );

    // if no terms then lets add our terms
    //if( empty( $alpha ) ){
    $alphas = get_default_alpha();
    foreach( $alphas as $alpha ){
    if( !term_exists( $alpha[‘name’], ‘alpha’ ) ){
    wp_insert_term( $alpha[‘name’], ‘alpha’, array() );
    }
    }
    //}

    }
    function get_default_alpha(){

    $alpha = array(
    ‘0’ => array( ‘name’ => ‘0-9’),
    ‘1’ => array( ‘name’ => ‘a’ ),
    ‘2’ => array( ‘name’ => ‘b’ ),
    ‘3’ => array( ‘name’ => ‘c’ ),
    ‘4’ => array( ‘name’ => ‘d’ ),
    ‘5’ => array( ‘name’ => ‘e’ ),
    ‘6’ => array( ‘name’ => ‘f’ ),
    ‘7’ => array( ‘name’ => ‘g’ ),
    ‘8’ => array( ‘name’ => ‘h’ ),
    ‘9’ => array( ‘name’ => ‘i’ ),
    ’10’ => array( ‘name’ => ‘j’ ),
    ’11’ => array( ‘name’ => ‘k’ ),
    ’12’ => array( ‘name’ => ‘l’ ),
    ’13’ => array( ‘name’ => ‘m’ ),
    ’14’ => array( ‘name’ => ‘n’ ),
    ’15’ => array( ‘name’ => ‘o’ ),
    ’16’ => array( ‘name’ => ‘p’ ),
    ’17’ => array( ‘name’ => ‘q’ ),
    ’18’ => array( ‘name’ => ‘r’ ),
    ’19’ => array( ‘name’ => ‘s’ ),
    ’20’ => array( ‘name’ => ‘t’ ),
    ’21’ => array( ‘name’ => ‘u’ ),
    ’22’ => array( ‘name’ => ‘v’ ),
    ’23’ => array( ‘name’ => ‘w’ ),
    ’24’ => array( ‘name’ => ‘x’ ),
    ’25’ => array( ‘name’ => ‘y’ ),
    ’26’ => array( ‘name’ => ‘z’ ),
    );

    return $alpha;

Leave a Reply