Once up a time i did a client project where i had to have archives organized alphabetically. I ended up accomplishing by adding a query variable and targeting the posts_where filter. However, in answering a recent question at WordPress Stack Exchange I decided that it might be neater to create a hidden taxonomy instead. I don’t know if there is any performance benefit, but the code was a little more elegant and you get prettier permalinks: site.com/glossary/a instead of site.com/?glossary=a right off the bat without the need to do any complicated htaccess rewrite rules, since everyone knows that mod-rewrite is straight up voodoo.
Creating a Hidden Taxonomy
Pretty standard function for registering a taxonomy. Just going to accept a lot of the defaults, and not worry about labels since we’re going to hide it from the back-end by setting the ‘show_ui’ parameter to false. For this example we’re going to call the taxonomy “glossary” and we’re going to assign it to posts, but we could well have done it for any custom post type.
// Add new taxonomy, NOT hierarchical (like tags)
function kia_create_glossary_taxonomy(){
if(!taxonomy_exists('glossary')){
register_taxonomy('glossary',array('post'),array(
'show_ui' => false
));
}
}
add_action('init','kia_create_glossary_taxonomy');
Automatically Setting Terms for each Post on Save/Update
It’d be a pain if we had to actively remember to sort each post by its letter each time we wrote a post. I have enough difficulty just writing a post in the first place. But with some code we can automatically pop off the first letter of the post title and assign that letter as the term in our “glossary” taxonomy. The following is the pretty standard function for saving information from a metabox, which works perfectly for our case even though we don’t have a visible metabox.
/* When the post is saved, saves our custom data */
function kia_save_first_letter( $post_id ) {
// verify if this is an auto save routine.
// If it is our form has not been submitted, so we dont want to do anything
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
//check location (only run for posts)
$limitPostTypes = array('post');
if (!in_array($_POST['post_type'], $limitPostTypes))
return $post_id;
// Check permissions
if ( !current_user_can( 'edit_post', $post_id ) )
return $post_id;
// OK, we're authenticated: we need to find and save the data
$taxonomy = 'glossary';
//set term as first letter of post title, lower case
wp_set_post_terms( $post_id, strtolower(substr($_POST['post_title'], 0, 1)), $taxonomy );
//delete the transient that is storing the alphabet letters
delete_transient( 'kia_archive_alphabet');
}
add_action( 'save_post', 'kia_save_first_letter' );
Auto-Assigning Terms to existing posts
If you’ve had a blog for a while and have a lot of posts the idea of going back and manually saving the posts again sounds worse than pulling our your toenails. So you can run a little function to do it automatically. Ideally, if this were a plugin, it’d run on the plugin’s activation hook. Since it’s not you can just drop it into your functions.php and then reload your site. Thanks to the transient check you can leave it in there and it won’t run again. Basically what it will do it grab all your posts, loop through them all, pop off the first letter of the title and assign it to the “glossary” taxonomy.
//create array from existing posts
function kia_run_once(){
if ( false === get_transient( 'kia_run_once' ) ) {
$taxonomy = 'glossary';
$alphabet = array();
$posts = get_posts(array('numberposts' => -1) );
foreach( $posts as $p ) :
//set term as first letter of post title, lower case
wp_set_post_terms( $p->ID, strtolower(substr($p->post_title, 0, 1)), $taxonomy );
endforeach;
set_transient( 'kia_run_once', 'true' );
}
}
add_action('init','kia_run_once');
Finally, Creating the Alphabet “Menu”
Now that we have assigned a term for each existing post and for every post to come in our custom taxonomy, we should display an alphabet menu…. or some way to access the different Letter archives. This was the one place were the code was not 10x more elegant than in my first iteration because there was no easy way to test whether a term had a post in it. Usually, yes, it would… but if you changed a post name and there were no other posts under a certain letter you could come up with an empty section. So what I did was get all the terms in the taxonomy with get_terms which hides empty terms by default, loop through that data and store it in an array, then check each letter of the alphabet against that array. To avoid running through that every single page load, I used the transient API to store the resulting array of alphabet terms. This array refreshes whenever a post is updated (this actually happens in an earlier code block for the kia_save_first_letter() function. For my original project I wanted to have the letters with posts have an active link and the letters without posts in that section just have the letter. Something like: A B C D** E** …. and onwards, so I have re-created that effect.
$taxonomy = 'glossary';
// save the terms that have posts in an array as a transient
if ( false === ( $alphabet = get_transient( 'kia_archive_alphabet' ) ) ) {
// It wasn't there, so regenerate the data and save the transient
$terms = get_terms($taxonomy);
$alphabet = array();
if($terms){
foreach ($terms as $term){
$alphabet[] = $term->slug;
}
}
set_transient( 'kia_archive_alphabet', $alphabet );
}
?>
<div id="archive-menu" class="menu">
<ul id="alphabet-menu">
<?php
foreach(range('a', 'z') as $i) :
$current = ($i == get_query_var($taxonomy)) ? "current-menu-item" : "menu-item";
if (in_array( $i, $alphabet )){
printf( '<li class="az-char %s"><a href="%s">%s</a></li>', $current, get_term_link( $i, $taxonomy ), strtoupper($i) );
} else {
printf( '<li class="az-char %s">%s</li>', $current, strtoupper($i) );
}
endforeach;
?>
</ul>
</div>
Future Improvements
One thing I’d like to improve upon would be ensuring that the first letter I’m popping off with the substr PHP function is actually a letter… or maybe a number, but not a character. I’d also like to get it to skip words like The, An, and other pointless words that don’t really reflect on the subject. But then this isn’t a solution for everyone… if you want to make sure that “A Post about Bacon” and “The Bacon Post” show up in the same archive, well you should tag them in the Bacon tag and not hope that they show up alphabetically in the right place. Still it has its uses. Let me know of any improvements you make!
Comments
68 responses to “Create an Alphabetical Glossary of Posts in WordPress”
Hi Kathy, Today i saw your website (kathyawesome). Your website provides really useful stuff but why are you using this simple theme (twentyeleven). Use a new theme, add some social follow icons and also add “About US” and “Contact us” page in navigation bar.
I Hope you like my suggestion.
Best Regards,
Damanjit Singh (TechWallz.COM)
@Damanjit Thank you for your suggestions. You know what they say about designing your own site? You have a pain in the ass for a client. It’s a work in progress, but there was some information I wanted to publish and didn’t want to wait on a custom theme.
Hi Kathy, you are pretty awesome. I’ve (and many other likely) been struggling with this some time now.
I’m trying the get the first letter of the second word. When setting the term to the first letter of post title. Just can’t figure out the right way.
posts}.post_title))), 0, 1)), $taxonomy );
?>
Lookin’ forward to your coming posts…
Here what I have, but doesn’t work for me:
wp_set_post_terms( $post_id, strtolower(substr(LOCATE(‘ ‘,LTRIM({$wpdb->posts}.post_title))), 0, 1)), $taxonomy );
gino, thanks for the compliment. as you can tell, i haven’t bothered to theme the site yet, so code in comments is a little shaky. i have to wonder what you are doing with
$wpdb->posts}.post_title
b/c that doesn’t really resemble my code at all… and indicates you are trying to do something with the database maybe?
i don’t really have time to solve your question, so the best i can tell you is to revisit what i have for wp_set_post_terms and play around with the php string matching. i’d suggest you do something on the front-end to just experiment with a static string until you get the right PHP combo.
Hi Kathy, I add explode(‘ ‘,x) to take first letter of the second word in the title.
$name_array = explode(‘ ‘, $_POST[‘post_title’]);
echo $name_array[1];
wp_set_post_terms( $post_id, strtolower(substr($name_array[1], 0, 1)), $taxonomy );
nice! i might be able to use that if i ever update this to skip “an”, “the”, “a” and other short, meaningless words.
Great tutorial, thank you very much, it is exactly I need for one of the movie database website
Glad it helped you! A movie database is a great application. Though the more I think about it the more it probably could be done without a taxonomy…. and definitely needs to consider how to handle a movie title like “The Sting”. My code will probably file that under “T” when it should be “S” for “Sting, The”. Aw well. Keep learning.
I added the taxonomy-box to editor screen, so author can check and change the letter. I now, it’s not the automatical way, but :-)
@AzzePis – Nicely done. I think this could be done totally with rewrite rules and custom queries, but whatever get the job done.
Hey Kathy, great work, trying to implement this with a custom post type created via the types plugin. When you make a custom post type you have the plural name and the singular, which should be referenced in your code instead of “post” I was guessing singular, but the code didn’t seem to work with my custom post type.
How many changes need to be made to your code? I changed two instances of “post” to “listing” but no joy. I noticed your final function to check current posts and update their alphabetical taxonomy entry doesn’t bare a reference to the post type? Is it because it is inherited from the code before it?
Thanks
Thanks David. When you register the post type… that’s where you set the name of the post type that you will need for this code.
register_post_type( 'bacon', $args );
So you’d refer to that type as “bacon”. I honestly don’t know how much needs changing, but I wouldn’t think much. All the code that references a post by its ID will work on any post type. I would also double check that your taxonomy is registered to your custom post type. Hope that helps.Hey Kathy, yeah, the issue I have with not knowing is because the post type is not manually registered, I use the types plugin for custom post types in some cases where there is a lot going on such as lots of different custom fields attributed to the new post type etc, and I am also working on the Thesis framework, I will have a look and see if the plugin gives more info as to which term is used as the global post type name for these purposes, I am pretty sure it is the plural with “Types”.
Thanks for your speedy reply, I admire your work!
You happened to catch me when I was procrastinating from what I should be doing. Maybe post a question to the Types people or in WordPress Answers. I am not actually sure that the base would be. Sorry I couldn’t be more helpful. You’ve caught me at a bit of a frazzled time.
Hi Kathy,
Is there anything special I need to do here to make /glossary/ work as a virtual directory (other than what you’ve provided)? I’m getting a 404 error when I attempt /glossary/v
Any thoughts?
Hi Jon. I don’t know what you mean by “virtual directory” but be sure to re-save your permalinks after initializing a custom taxonomy. Sorry, but I can’t provide much support on this post.
Hi Kathy,
You’re so clever! This is most likely what I’m after for a Woocommerce site I’ve wound up building, but one question, would this work for the product,not sure if they are handled the same way as posts? Fairly new to wordpress as you can see!
Many thanks for a great article.
Dennis
To elaborate a little, it’s a second hand book site, so being able to list by author, title is kind of essential, and each more or less unique book is listed as a product.
Cheers
Dennis
Dennis,
This concept should work just fine for products. You’ll just have to adapt your taxonomy and some of my code from the ‘post’ post type to the ‘product’ post type. Regarding your ‘books’ you might also want an author taxonomy.
Hi Kathy,
I love your alphabetical post taxonomy article and was wondering how do I get it to just index posts that belongs in a specific category and not all posts?
Hi Lee, Thanks for stopping by. I am sure this is possible, but it isn’t exactly easy either, so I couldn’t tell you off the top of my head. Instead of a specific category, I think it’d probably be easier to switch to a custom post type.
Hi Kathy. Awesome tutorial for real! I just wanted to know how would get it to work for custom post type? Would I simply change the following line
register_taxonomy('glossary',array('post'))
, toregister_taxonomy('glossary',array('custom-post-type'))
, and then change any reference to ‘post’ to ‘custom-post-type’ where it appears in your example above? Thanks.It has been a long while since I’ve looked at this code, but yes, I assume that you’d change any reference to “post” to your CPT.
Lastly, where does the code for “Creating the Alphabet Menu” go? In functions.php or in the template using php?
As written it goes in the template where you want to display it. Nothing stopping you from wrapping it in a function and adding it to a hook if your parent theme has appropriate hooks and you are making a child theme.
Kathy,
Thanks for this post! It was the best post I found on this and very well explained. One question though. Could I modify this to use a range such as A-D, E-H and so on? Thanks again.
Hi Scott, I’m glad you found it useful. Almost assuredly, the code could be altered to use ranges. You’d have to change the menu and how the tax data is saved…. to save terms such as “a-e” instead of simply “e”, but it is definitely doable.
So I would need to change this section of the function?
wp_set_post_terms( $post_id, strtolower(substr($\_POST['post\_title'], 0, 1)), $taxonomy );
and this in the template.foreach(range('a', 'z') as $i) :
Apologize for my ignorance, but first time attempting something like this.Hi Kathy,
I need posts of one of custom posts types {medical glossary} as Alphabetical filter
A B C D….etc (or) search form like dictionary if we type A/a the it shows list of posts under A name.. like that
A
Apost 1
B
Bpost 2
…
Please give me suggestions how to display those things.
I have created custom posts types as medical glossary which post-type= disease
so , i need to show all these things and one more challenge is to display these things in a page.. {i.e tempalte}.
Please help me kathy.
I am eager for your reply..
Thanks in advance..
TC
Hi TC,
That is more support than I can provide for free in the comments. Try reading the codex, specifically the Orderby parameters and using the support forums.
Has anyone managed to get this working with custom post types? I am trying but I cant work it out.
Can anyone post their code?
Thanks for your help!
To use for a particular post type you should only need to
1. register the taxonomy for that post type
1. change the save routine’s
$limitPostTypes
array1. change the
get_posts
args in thekia_run_once()
(only needed if you have existing posts)But I’m not going to test it, so your mileage may vary. Good luck.
Thank you for your quick and useful reply! I made the changes you said and ran the code. It generated the alphabet but without any links. So I understand there must be an issue with populating the $alphabet array. I change the code there to accommodate CPT to
$args = array( "numberposts" => -1, "post_type" => "directory-listing" );
$posts = get_posts($args);
could you point me in the direction of where the issue may be? Thanks so much
I can’t really look into it, but I think you’re on the right path. I would delete the $alphabet transient and maybe disable the
set_transient()
part until you figure it out.It also occurred to me while I was out for a walk that
get_terms()
only gets terms that have at least one post with that term assigned (unless you change the arguments). As such, if you don’t have any posts yet in a particular term, you won’t get a link. Maybe that helps and maybe it doesn’t.Hi Kathy This is exactly what I’ve been looking for (and great site too btw!). The problem I’m having though is that instead of using the first letter of the post title I would like to use the first letter of a custom field. I’m using this for a bibliography and so want to order my posts by the ‘author_surname’ custom field for a custom post type called ‘publications’. Here’s the code I’m trying to use. Can you see where I might be going wrong? Would appreciate any help you could provide. Many thanks D
Hi Damien. Thanks for stopping by. I can’t really provide free customization support in the comments and as I’m in training camp right now I can’t provide paid support at the moment either. Do remember that
get_posts()
only gets posts by default and always make sure things work before setting transients. Good luck.Hi Kathy
No problem. I’ll figure it out I’m sure!
Thanks
D
Thanks a lot Kathy!
Simple and works nicely.
This is really great! Thanks so much for sharing Kathy!!
Hey Kathy i ve got a problem using this with greek posts! I need to use the greek alphabet not the english one but i cant make it happen. It shows me character codes like 208 210 etc instead of letters. For instance if i use it with English alphabet i get exactly the result i want but when i use it with the greek posts i have instead of Α, Β, Γ, Δ , Ε Ζ … => 208, 209 ,210 what can i do?
Hi Thanasis,
I have to admit to being a little weak on internationalization stuff. Do you mean your menu doesn’t look right? It could be that
foreach(range('a', 'z') as $i) :
needs to be adapted to something that loops through the greek alphabet instead. If it isn’t saving correctly, I’m at more of a loss since it ought to save characters in your site’s language.Yes it is exactly like that…and if i change the foreach(range(‘a’, ‘z’) as $i) to foreach($alphabet as $i) i get the codes i was telling you!anyway thank you for your help and your great post!!
You’re welcome!
Well
$alphabet
is not the actual alphabet, but rather a list of all the terms in the alphabet taxonomy (so all the first letters that have posts). If the greek first letter isn’t able to be saved as a term name, well I’m stumped. (Though maybe it has something to do withsanitize_title()
?)range()
does not yet support unicode (PHP6.0 supposedly so who knows!), so you can’t use that to create the list. You could however, write your own array and use that. for example:Ok i ll do that. Thank you so much Kathy
First, Thaks you for this code. It’s very good idea. But I have problem like thanasis. I have instead of Ə, Ü, Ç, Ş , Ö, Ğ characters my posts. And create massive:
$symbols = array('a', 'b', 'c', 'ç', 'd', 'e', 'ə', 'f', 'g', 'h', 'x', 'İ', 'i', 'j', 'k', 'q', 'l', 'm', 'n', 'o', 'ö', 'p', 'r', 's', 'ş', 't', 'u', 'ü', 'v', 'y', 'z');
Latin characters(a-z) get link, but my special characters not have link.
Hi Bahruz,
Unfortunately, I don’t know the answer. I’m not well-versed in non-English characters. If they show up unlinked, it is possible that the first letter isn’t saving as a character? If you figure it out please do post back so I can update the post.
-k
A very nifty approach. Awesome indeed!!!
Hi,
Thank you for posting this tutorial. It is super useful. It worked great, except the letters were not live links. Where could I have gone wrong?
Thank you,
Brendon
It has been ages since I wrote this, but if I had to guess you don’t have actual links because those letters don’t have any posts in their archive. Have you written any posts yet? There’s also the transient I was using to save the terms so if you do have posts, try re-saving a post to clear the transient. And make sure you ran the “run once” code.
KATHY IS AWESOME! Thank you.
Hi Kathy!
First of all, thank you for this post! It’s awesome!
I get the alphabet list but my problem is when I make a click on a letter I go to the first post of the letter list instead of the archive page listing the posts with that letter.
What can I do? Thank you in advance!
You’re welcome Miguel. I don’t know what you mean exactly, but
printf( '<li class="az-char %s"><a href="%s">%s</a></li>', $current, get_term_link( $i, $taxonomy ), strtoupper($i) );
should be printing a link like :
www.yoursite.com/glossary/a
If that isn’t resolving to an archive you might try re-saving your permalinks settings.
Hey Kathy! Thanks for this awesome bit of code. I made a few changes for my use case in which the customer wanted ranges of the alphabet, so I made a small function that takes a string and returns strings like “a-f”. I then integrated it with existing filtering code by hiding the “glossary” select (with visibility hidden and position absolute) and added code to fetch all the terms and display links that triggered value changes in the hidden select before using jquery to submit the form again.
Thanks again for the useful post!
Hi Stephen, that sounds really cool. Thanks for letting me know what you’ve done with my code.
Hi Kathy
I want to thank you for the useful article that helped me build my new site. I want to ask you how you can create an array with 0-9, while maintaining that Article, any nubmer, it shows on links 0-9.
For example, 0-9, A, B, C, D, …
Article name: 5-NOK must be shown in 0-9
Thanks in advance)))
Hi I have some questions:
I didn’t correctly get how do we use your code? Where do we use these functions? and do we have to use all the aformentioned functions or only some of them?
thanks,
You need all the functions as they all work together to handle various aspects of the solution. I’d like to say they should all go into a plugin, but the menu definitely needs to go in your theme somewhere (on the archive template for whatever post type you are using this one… the example is for posts), so it would probably be easiest to put everything else in your theme’s
functions.php
.Hi Kathy! thanks for your answer!
Well I’m trying your stuff for posts so it’s fortunate…
So to be clear, I need to use it all in the functions.php file then ??
No, everything goes in
functions.php
except the last code block which contains the menu. That needs to go in the appropriate template…. could beindex.php
could bearchive.php
. I can’t know for sure because everything theme is different. If you aren’t already you should be using a child theme to make these types of modifications.Hi Kathy!
Oh ok! got it now! I think I know what was my mistake using your code…I’ll try it more properly….
thanks a lot for your clarifications ;)
Love this – but I have an odd issue – first of all I can’t save values for posts via quick edit. I select multiple items and when I update the alpha field and save – they do not get saved.
Secondly, I accidentally cleared out all values of alpha for 435 posts – when I try to run the kia_run_once function again (to reassign all of the posts to the alpha taxonomy) nothing happens. I’ve tried modifying it so that it’s a different function, with a different set_transient value, etc.. but nothing. Any ideas?
Hi there, I’m glad you like this post. Unfortunately, it’s pretty old so I’m no longer super familiar with it. It doesn’t have quick edit support, someone would need to write that. As for the run once function you should be able to simply delete the transient
delete_transient( 'kia_run_once' );
and on the next load it would run.Hi, Thanks for your precious article, I have a big problem to solve.
In my site there are different type of archive pages.
and the archive.php is not the right one.
I need to redirect the plug in to a specific archive.php file. How can I do that?
Thank you so much
Nevermind I just solved the problem creating a taxonomy-[taxonomy-slug].php file
:)
Hi Kate,
I have a website and I don’t know how they came up with the grouping – I want to do exactly the same
https://www.thepower50.com/the-nominees/
I’m sure you can help me on this. please I wouldn’t mind you reaching me soonest as it’s very urgent. Can you reach me through my mail?. Thanks
Marcus,
I’m not available for projects right now. The tutorial is fairly complete and hopefully it will walk you through the process.
Cheers,
-kathy