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.
Lessons
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().
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.
| Function | Output |
|---|---|
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 |
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;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 );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.'paged' => $paged in a custom query — every page shows the first six posts.the_posts_pagination() to index.php.paginate_links()..current class WordPress adds.In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.