WordPress Custom Query Page Size

WordPress normally takes care of queries for you. When it doesn’t, your next port of call is normally WP_Query. After that, you head into nuts-and-bolts mode with a completely custom query using get_posts. When you start taking control over queries, you may hit this common problem with page size.

Here is an example of custom query arguments that are going to be used with either WP_Query or get_posts.

$custom_page_size = 3;

$my_arguments = array(
    'post_type' => 'custom',
    'posts_per_page' => $custom_page_size,
    'offset' => ($paged - 1) * $custom_page_size,
    'meta_key' => 'is_flagged',
    'meta_value' => '0'
);

The important bit here, which will cause you pain, is that there is a $custom_page_size.

This may appear to work, especially if you aren’t testing your edges. The problem occurs when the number of posts divided by the default page size is less than the number of posts divided by your custom page size.

The Problem with the Custom Page Sizes

An example is needed, here is an example with 12 posts.

posts = 12
default page size = 5
custom page size = 3

posts / default page size = 3 pages

posts / custom page size = 4 pages

Everything will work on pages 1, 2, and 3. When you reach page 4 though, you’ll get a 404 Not Found error page.

This is because WordPress has worked out that page 4 can’t exist based on the number of posts and the default page size.

The Solution

The solution to this problem is that you shouldn’t define the page size local to your custom query. You should define it somewhere that will change the for both the local query and for the check made by WordPress. You can do this by hooking into pre_get_posts, like this example from the themes functions.php file.

function custom_page_sizes($query) {
    if (!is_admin()) {
        if(is_post_type_archive('custom')) {
            $query->set('posts_per_page', 3);
        }
    }
}
add_action('pre_get_posts', 'custom_page_sizes');

This allows you to use the default $posts_per_page variable in your custom queries, and the number will be the one you defined in the pre_get_posts hook.

$my_arguments = array(
    'post_type' => 'custom',
    'posts_per_page' => $posts_per_page,
    'offset' => ($paged - 1) * $posts_per_page,
    'meta_key' => 'is_flagged',
    'meta_value' => '0'
);