To make sticky posts work on archives I had to dive deep into the WP_Query::get_posts() code. And I was able to steal the hard part from WordPress.
A client requested that certain posts be at the top of their category archives. I suggested that he set the posts as Sticky. Unfortunately this didn’t work – it turns out that the sticky post feature only applies on the home page.
Diving into WP_Query::get_posts()
My initial attempt to solve this problem was to use the ‘pre_get_posts‘ filter to set ‘ignore_sticky_posts‘ to ‘false.’ This didn’t work because, as mentioned above, the feature is limited to the home page.
I looked further down the very long function get_posts() function (it’s over 1,300 lines!) and found the ‘the_posts‘ filter. It returns a list of fetched posts. I did a few tests (like array_unshift() a post to the top of the list) and saw that it was the right filter.
I copied the sticky posts code from the WP_Query::get_posts() function (I knew that it would work – why reinvent the wheel?) and my code worked!
I further enhanced my code so that it only applied to specified categories. When I converted the code to a class in its own plugin I provided a filter to change the category list.
I also found that some queries set ‘suppress_filters‘ to true thus disabling filters like ‘the_posts‘, but, thankfully the main query does not have this set (and it is then initialised to ‘false‘). So, I didn’t need to use ‘pre_get_posts‘ to set it to ‘false.’
This also means that the ‘the_posts‘ filter is only called once (or twice – that’s what I saw during testing!) per page load. Even so, I try to quit the function as soon as possible so that my code doesn’t slow down the query.
The ‘sticky_posts‘ option is an array of post IDs. While the ‘the_posts‘ filter provides an array of post objects, it is okay to add post IDs to the array because the returned array will be passed through the ‘get_post()‘ function via an ‘array_map()‘ call. This will convert IDs to post objects. The copied code to handle the sticky posts moves post objects around so this is not an issue – but good to know for the future.
By default the code will operate on all categories. You can use the ‘sia_sticky_categories‘ filter to limit it to the categories you specify.
Plugin on GitHub
You may download the full plugin from my sticky-in-archives repository.
Update – Better Solutions
I quickly found that my plugin was not moving the sticky post to the top for some categories. When the sticky post was not in the original loop then it wasn’t available to be promoted.
I found two plugins on the WordPress repository. Sticky Posts in Category is very similar to mine, with the same issue.
Category Sticky Post is a much better and solid solution. For each post you can set the category in which it will be sticky. It does not use the core Sticky feature. I installed this on my client’s site.