paginate_links()
Allows creating pagination links for any pages.
An example of how pagination looks: « previous 1 … 3 4 5 6 7 … 9 next »
Technically, the function can be used to create pagination anywhere. This function is the core for all pagination functions in WordPress.
There are also special functions (wrappers for this function) for building pagination in WordPress:
This function rigidly adds all $_GET parameters of the current request (current page) to the pagination links. Therefore, for example, if it is used in an AJAX request handler, all URLs in it will contain the current GET parameters. Ticket on this topic.
A bit about parameters
The parameter total should receive the total number of pagination pages, and the parameter current the number of the current pagination page.
An example of the base parameter - http://example.com/all_posts.php%_%, where %_% will be replaced by what is specified in the parameter format = '?page=%#%', here %#% will be replaced by the number of the current pagination page.
In general, in base you can specify it like this: http://example.com/all_posts.php?page=%#%, and at the same time format = '' (empty).
You can add query variables to the links, for this specify the required variables and their values as an array in the add_args parameter. For more details, see the function add_query_arg().
To add links to the previous/next page, you need to enable the boolean parameter prev_next (set it to true), and then you can set the link text by specifying the parameters prev_text / next_text (previous link/next link).
Hooks from the function
Returns
String|String[]|null.
string- HTML or a string of pagination links. Whentype = plainortype = list. By default.array- An array of pagination link data for further processing in PHP. Whentype = array.null- When the total number of pagination pages is less than 2.
Usage Template
$args = [
'base' => '%_%',
'format' => '?page=%#%',
'total' => 1,
'current' => 0,
'show_all' => False,
'end_size' => 1,
'mid_size' => 2,
'prev_next' => True,
'prev_text' => __('« Previous'),
'next_text' => __('Next »'),
'type' => 'plain',
'add_args' => False,
'add_fragment' => '',
'before_page_number' => '',
'after_page_number' => ''
];
echo paginate_links( $args );
Usage
paginate_links( $args );
- $args(string/array)
- Arguments for building pagination.
Default: presets
Arguments of the $args parameter
- base(string)
The base URL format that will be used to create the pagination link. For example:
http://example.com/all_posts.php%_%here%_%will be replaced by the value of the format argument.In
baseyou can specify it like this:http://example.com/all_posts.php?page=%#%, and at the same time specify an empty string informat.Default: '%_%'
- format(string)
- The replacement format.
Default: '?page=%#%' - total(number)
- The total number of pages involved in pagination.
Default: 1 - current(number)
- The number of the current pagination page.
- show_all(boolean)
- true - all pages involved in pagination will be shown.
false - (By default) only a few links before and after the current page number are shown. The number of links is regulated by the parametersend_sizeandmid_size.
Default: false - end_size(number)
- How many links to show from the beginning and end:
"previous 1 2 ... [4] ... 8 9 next".
Default: 1 - mid_size(number)
- How many numbers to show before and after the current number:
1 ... 2 3 [4] 5 6 ... 99.
Default: 2 - prev_next(boolean)
- Output side links "previous/next page". By default, they are output if these links are NOT to be output, write false.
Default: true - prev_text(string)
- The text of the "previous page" link.
Default: __('« Previous') - next_text(string)
- The text of the "next page" link.
Default: __('Next »') - type(string)
Controls in what format the result will be returned:
plain- just links separated by space (By default).array- in the form of an array of data for further processing in PHP.list- <ul> list.
Default: 'plain'
- add_args(array)
- An array of query variables that need to be added to the pagination links. See the function add_query_arg().
Default: array() - add_fragment(string)
- Text that will be added to all links.
Default: '' - aria_current(string) (WP 4.9)
- Value of the
aria-currentattribute. Possible values: 'page', 'step', 'location', 'date', 'time', 'true', 'false'.
Default: 'page'. - before_page_number(string)
- Text/string before the pagination number.
- after_page_number(string)
Text/string after the pagination number.
The parameters before_page_number and after_page_number allow wrapping the pagination number itself. For example, in a <span> tag for styling.
In general, these parameters were created to specify text for robots, so that when viewing the code, it was clear what the links are intended for.
Examples
#1 Pagination, analogous to wp_pagenavi
To add pagination to a search results page or an archives page, use this code:
function my_pagenavi() {
global $wp_query;
$big = 999999999; // unique (int) to replace
$args = array(
'base' => str_replace( $big, '%#%', get_pagenum_link( $big ) ),
'format' => '',
'current' => max( 1, get_query_var('paged') ),
'total' => $wp_query->max_num_pages,
);
$result = paginate_links( $args );
// remove the pagination add-on for the first page
$result = preg_replace( '~/page/1/?([\'"])~', '', $result );
echo $result;
}
Now, where you want to output the pagination:
my_pagenavi();
#2 Alternative to this function
This function always returns HTML, even if parameter What the function outputs: Now use this function in the loop: We get it:type=array you get an array of ready-made <a> tags. This may not work when you need to completely change the HTML structure of your pagination. Below is a small function that returns an array of objects instead of HTML./**
* Generates array of pagination links.
*
* @author Kama (wp-kama.com)
* @varsion 2.5
*
* @param array $args {
*
* @type int $total Maximum allowable pagination page.
* @type int $current Current page number.
* @type string $url_base URL pattern. Use `{pagenum}` placeholder.
* @type string $first_url URL to first page. Default: '' - taken automaticcaly from $url_base.
* @type int $mid_size Number of links before/after current: 1 ... 1 2 [3] 4 5 ... 99. Default: 2.
* @type int $end_size Number of links at the edges: 1 2 ... 3 4 [5] 6 7 ... 98 99. Default: 1.
* @type bool $show_all true - Show all links. Default: false.
* @type string $a_text_patt `%s` will be replaced with number of pagination page. Default: `'%s'`.
* @type bool $is_prev_next Whether to show prev/next links. « Previou 1 2 [3] 4 ... 99 Next ». Default: false.
* @type string $prev_text Default: `« Previous`.
* @type string $next_text Default: `Next »`.
* }
*
* @return array
*/
function kama_paginate_links_data( array $args ): array {
global $wp_query;
$args += [
'total' => 1,
'current' => 0,
'url_base' => '/{pagenum}',
'first_url' => '',
'mid_size' => 2,
'end_size' => 1,
'show_all' => false,
'a_text_patt' => '%s',
'is_prev_next' => false,
'prev_text' => '« Previous',
'next_text' => 'Next »',
];
$rg = (object) $args;
$total_pages = max( 1, (int) ( $rg->total ?: $wp_query->max_num_pages ) );
if( $total_pages === 1 ){
return [];
}
// fix working parameters
$rg->total = $total_pages;
$rg->current = max( 1, abs( $rg->current ?: get_query_var( 'paged', 1 ) ) );
$rg->url_base = $rg->url_base ?: str_replace( PHP_INT_MAX, '{pagenum}', get_pagenum_link( PHP_INT_MAX ) );
$rg->url_base = wp_normalize_path( $rg->url_base );
if( ! $rg->first_url ){
// /foo/page(d)/2 >>> /foo/ /foo?page(d)=2 >>> /foo/
$rg->first_url = preg_replace( '~/paged?/{pagenum}/?|[?]paged?={pagenum}|/{pagenum}/?~', '', $rg->url_base );
$rg->first_url = user_trailingslashit( $rg->first_url );
}
// core array
if( $rg->show_all ){
$active_nums = range( 1, $rg->total );
}
else {
if( $rg->end_size > 1 ){
$start_nums = range( 1, $rg->end_size );
$end_nums = range( $rg->total - ($rg->end_size - 1), $rg->total );
}
else {
$start_nums = [ 1 ];
$end_nums = [ $rg->total ];
}
$from = $rg->current - $rg->mid_size;
$to = $rg->current + $rg->mid_size;
if( $from < 1 ){
$to = min( $rg->total, $to + absint( $from ) );
$from = 1;
}
if( $to > $rg->total ){
$from = max( 1, $from - ($to - $rg->total) );
$to = $rg->total;
}
$active_nums = array_merge( $start_nums, range( $from, $to ), $end_nums );
$active_nums = array_unique( $active_nums );
$active_nums = array_values( $active_nums ); // reset keys
}
// fill by core array
$pages = [];
if( 1 === count( $active_nums ) ){
return $pages;
}
$item_data = static function( $num ) use ( $rg ){
$data = [
'is_current' => false,
'page_num' => null,
'url' => null,
'link_text' => null,
'is_prev_next' => false,
'is_dots' => false,
];
if( 'dots' === $num ){
return (object) ( [
'is_dots' => true,
'link_text' => '…',
] + $data );
}
$is_prev = 'prev' === $num && ( $num = max( 1, $rg->current - 1 ) );
$is_next = 'next' === $num && ( $num = min( $rg->total, $rg->current + 1 ) );
$data = [
'is_current' => ! ( $is_prev || $is_next ) && $num === $rg->current,
'page_num' => $num,
'url' => 1 === $num ? $rg->first_url : str_replace( '{pagenum}', $num, $rg->url_base ),
'is_prev_next' => $is_prev || $is_next,
] + $data;
if( $is_prev ){
$data['link_text'] = $rg->prev_text;
}
elseif( $is_next ) {
$data['link_text'] = $rg->next_text;
}
else {
$data['link_text'] = sprintf( $rg->a_text_patt, $num );
}
return (object) $data;
};
foreach( $active_nums as $indx => $num ){
$pages[] = $item_data( $num );
// set dots
$next = $active_nums[ $indx + 1 ] ?? null;
if( $next && ($num + 1) !== $next ){
$pages[] = $item_data( 'dots' );
}
}
if( $rg->is_prev_next ){
$rg->current !== 1 && array_unshift( $pages, $item_data( 'prev' ) );
$rg->current !== $rg->total && $pages[] = $item_data( 'next' );
}
return $pages;
}
$links_data = kama_paginate_links_data( [
'total' => 3,
'current' => 2,
'url_base' => 'http://site.com/page-name/paged/{pagenum}',
'mid_size' => 2,
] );
print_r( $links_data );
/*
Array
(
[0] => stdClass Object
(
[is_current] =>
[page_num] => 288
[url] => http://site.com/page-name/paged/288
[is_prev_next] => 1
[link_text] => « Previous
[is_dots] =>
)
[1] => stdClass Object
(
[is_current] =>
[page_num] => 1
[url] => http://site.com/page-name/
[is_prev_next] =>
[link_text] => 1
[is_dots] =>
)
[2] => stdClass Object
(
[is_dots] => 1
[link_text] => …
[is_current] =>
[page_num] =>
[url] =>
[is_prev_next] =>
)
[3] => stdClass Object
(
[is_current] =>
[page_num] => 285
[url] => http://site.com/page-name/paged/285
[is_prev_next] =>
[link_text] => 285
[is_dots] =>
)
[4] => stdClass Object
(
[is_current] =>
[page_num] => 286
[url] => http://site.com/page-name/paged/286
[is_prev_next] =>
[link_text] => 286
[is_dots] =>
)
[5] => stdClass Object
(
[is_current] => 1
[page_num] => 287
[url] => http://site.com/page-name/paged/287
[is_prev_next] =>
[link_text] => 287
[is_dots] =>
)
)
*/
<?php
$links_data = kama_paginate_links_data( [
'total' => 3,
'current' => 2,
'url_base' => 'http://site.com/page-name/paged/{pagenum}',
] );
if( $links_data ){
?>
<ul>
<?php foreach( $links_data as $link ) { ?>
<li>
<?php if ( $link->is_current ) { ?>
<strong><?php _e( $link->page_num ) ?></strong>
<?php } else { ?>
<a href="<?php esc_attr_e( $link->url ) ?>"><?php _e( $link->page_num ) ?></a>
<?php } ?>
</li>
<?php } ?>
</ul>
<?php
}
<ul>
<li>
<a href="http://site.com/page-name/paged/1">1</a>
</li>
<li>
<strong>2</strong>
</li>
<li>
<a href="http://site.com/page-name/paged/3">3</a>
</li>
</ul>
#3 Example with an arbitrary WP_Query query
When the posts are output by a separate query using new WP_Query, you can set the parameter total, in which you specify the WP_Query::$max_num_pages property to output pagination. Let's look at an example:
This is just a demo, because it doesn't take into account the main request, which may result in a 404 page and this code won't get to it at all. Also, it may not work correctly on a separate page of posts.
<?php
$paged = max( 1, (int) get_query_var('paged') );
$the_query = new WP_Query( [
'posts_per_page' => 5,
'category_name' => 'gallery',
'paged' => $paged,
] );
while( $the_query->have_posts() ){
$the_query->the_post();
?>
<!-- HTML of each post -->
<?php
}
wp_reset_postdata();
// pagination for custom query
$big = 999999999; // unique number
echo paginate_links( array(
'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
'current' => $paged,
'total' => $the_query->max_num_pages
) );
?> #4 Pagination for custom WP_Query
In the example below we will display WooCommerce products (post_type=product) on a separate post page (post_type=post). I.e. we will use arbitrary query and make pagination for it. We specified to output 5 products, if there are 22, for example, then there will be 5 pagination elements, 4 of which will be links of the following kind: Note: In the parameter Use $paged = max( 1, get_query_var('page') );
$query = new WP_Query( [
'post_type' => 'product',
'posts_per_page' => 5,
'paged' => $page,
] );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
// your code here
the_title();
}
wp_reset_postdata();
// Display pagination if the number of products is greater than the requested amount
echo paginate_links( [
'base' => user_trailingslashit( wp_normalize_path( get_permalink() .'/%#%/' ) ),
'current' => $page,
'total' => $query->max_num_pages,
] );
}
Current page, no link displayed (5 products)
http://example.com/название-записи/2/ (5 products)
http://example.com/название-записи/3/ (5 products)
http://example.com/название-записи/4/ (5 products)
http://example.com/название-записи/5/ (2 products)
base where the pagination link view is formed, do not use words like page or paged. Because with page there will be a redirect from the pagination page to the post itself, and with paged there will be a 404 error on the pagination page.get_query_var( 'paged' ) instead of the usual get_query_var( 'page' ) to get the pagination page number.<!--nextpage--> tag, more details see here.
#5 Improving Accessibility
To add context to the numbered links to ensure that screen reader users understand what the links are for:
global $wp_query;
$big = 999999999; // need an unlikely integer
$translated = __( 'Page', 'mytextdomain' ); // Supply translatable string
echo paginate_links( array(
'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
'format' => '?paged=%#%',
'current' => max( 1, get_query_var('paged') ),
'total' => $wp_query->max_num_pages,
'before_page_number' => '<span class="screen-reader-text">'. $translated .'</span>',
'prev_text' => is_rtl() ? 'Previous Page →' : 'Previous Page ←',
'next_text' => is_rtl() ? 'Next Page ←' : 'Next Page →',
) ); #6 Add a prefix for all classes BEM
Let's say we changed the navigation template via the navigation_markup_template hook and now we want all nested elements to correspond to BEM classes.
We have the BEM block class hlpagination:
<?php
/// change the navigation (pagination) template
add_filter( 'navigation_markup_template', 'hl_navigation_template', 10, 2 );
function hl_navigation_template( $template, $class ){
ob_start();
?>
<nav class="hlpagination %1$s" role="navigation">
<div class="hlpagination__nav-links">%3$s</div>
</nav>
<?php
return ob_get_clean();
}
%3$s is replaced by what paginate_links() returns and all classes must also have a prefix added:
/// fix html pagination for paginate_links()
add_filter( 'paginate_links_output', 'hl_fix_paginate_links' );
function hl_fix_paginate_links( $html ){
$html = preg_replace_callback( '/ class=[\'"][^\'"]+[\'"]/', static function( $mm ){
return strtr( $mm[0], [
'current' => '--current',
'prev' => 'hlpagination__prev',
'next' => 'hlpagination__next',
'dots' => 'hlpagination__dots',
'page-numbers' => 'hlpagination__numbers',
] );
}, $html );
return $html;
}
Notes
- Global. WP_Query.
$wp_queryWordPress Query object. - Global. WP_Rewrite.
$wp_rewriteWordPress rewrite component.
Changelog
| Since 2.1.0 | Introduced. |
| Since 4.9.0 | Added the aria_current argument. |