SponsoredHosinger Special Offer

WP WordPress Theme Development

Build classic and block WordPress themes from scratch: the template hierarchy, The Loop, the_title() and the_content(), template parts, menus, the Customizer, child themes and theme.json.

Pagination

WordPress Theme Development Lesson 20 of 30 ~3 min read

Overview

WordPress splits long lists of posts into pages according to Settings → Reading → Blog pages show at most, and your theme prints the links between those pages with the_posts_pagination().

Main query pagination

On index.php, archive.php and search.php, one line is enough:

<?php
the_posts_pagination(
    array(
        'mid_size'           => 2,
        'prev_text'          => esc_html__( '← Newer', 'gdc-starter' ),
        'next_text'          => esc_html__( 'Older →', 'gdc-starter' ),
        'screen_reader_text' => esc_html__( 'Posts navigation', 'gdc-starter' ),
    )
);

It prints a <nav> with a screen-reader heading and numbered links, marks the current page with aria-current="page", and prints nothing at all if there is only one page.

FunctionOutput
the_posts_pagination()Numbered: « 1 2 3 … 9 »
the_posts_navigation()Just "Older posts" and "Newer posts"
the_post_navigation()Previous and next single post — for single.php

Pagination for a custom WP_Query

the_posts_pagination() only knows about the main query. For a custom query, use paginate_links() and tell it the total from your own query object:

<?php
$paged = max( 1, get_query_var( 'paged' ) );

$projects = new WP_Query(
    array(
        'post_type'      => 'project',
        'posts_per_page' => 6,
        'paged'          => $paged,
    )
);

if ( $projects->have_posts() ) :
    while ( $projects->have_posts() ) :
        $projects->the_post();
        get_template_part( 'template-parts/content', 'project' );
    endwhile;

    $links = paginate_links(
        array(
            'total'     => $projects->max_num_pages,
            'current'   => $paged,
            'mid_size'  => 2,
            'prev_text' => esc_html__( '← Previous', 'gdc-starter' ),
            'next_text' => esc_html__( 'Next →', 'gdc-starter' ),
        )
    );

    if ( $links ) {
        echo '<nav class="pagination" aria-label="' . esc_attr__( 'Projects', 'gdc-starter' ) . '">' . $links . '</nav>';
    }

    wp_reset_postdata();
endif;

The static front page exception

On a page set as the static front page, WordPress stores the page number in the page query variable, not paged. Read both so the same code works everywhere:

<?php
$paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : get_query_var( 'page' );
$paged = max( 1, (int) $paged );

Common mistakes

  • Changing posts_per_page in a template's custom query for a main archive. Page 2 then 404s, because WordPress calculated the number of pages from the original setting. Use pre_get_posts (lesson 18) instead.
  • Forgetting 'paged' => $paged in a custom query — every page shows the first six posts.
  • Pagination that 404s after you change permalinks. Visit Settings → Permalinks and click Save to flush the rewrite rules.

Practice

  • Set "Blog pages show at most" to 3 and add the_posts_pagination() to index.php.
  • Build a paginated custom query with paginate_links().
  • Style the current page using the .current class WordPress adds.
Create a free account to save which lessons you've finished. Save my progress