paginate_links( string|array $args =  ): string|string[]|void

Retrieves paginated links for archive post pages.

Description

Technically, the function can be used to create paginated link list for any area. The ‘base’ argument is used to reference the url, which will be used to create the paginated links. The ‘format’ argument is then used for replacing the page number. It is however, most likely and by default, to be used on the archive post pages.

The ‘type’ argument controls format of the returned value. The default is ‘plain’, which is just a string with the links separated by a newline character. The other possible values are either ‘array’ or ‘list’. The ‘array’ value will return an array of the paginated link list to offer full control of display. The ‘list’ value will place all of the paginated links in an unordered HTML list.

The ‘total’ argument is the total amount of pages and is an integer. The ‘current’ argument is the current page number and is also an integer.

An example of the ‘base’ argument is "http://example.com/all_posts.php%_%" and the ‘%_%’ is required. The ‘%_%’ will be replaced by the contents of in the ‘format’ argument. An example for the ‘format’ argument is "?page=%#%" and the ‘%#%’ is also required. The ‘%#%’ will be replaced with the page number.

You can include the previous and next links in the list by setting the ‘prev_next’ argument to true, which it is by default. You can set the previous text, by using the ‘prev_text’ argument. You can set the next text by setting the ‘next_text’ argument.

If the ‘show_all’ argument is set to true, then it will show all of the pages instead of a short list of the pages near the current page. By default, the ‘show_all’ is set to false and controlled by the ‘end_size’ and ‘mid_size’ arguments. The ‘end_size’ argument is how many numbers on either the start and the end list edges, by default is 1. The ‘mid_size’ argument is how many numbers to either side of current page, but not including current page.

It is possible to add query vars to the link by using the ‘add_args’ argument and see add_query_arg() for more information.

The ‘before_page_number’ and ‘after_page_number’ arguments allow users to augment the links themselves. Typically this might be to add context to the numbered links so that screen reader users understand what the links are for.
The text strings are added before and after the page number – within the anchor tag.

Parameters

$argsstring|arrayoptional
Array or string of arguments for generating paginated links for archives.
  • base string
    Base of the paginated url.
  • format string
    Format for the pagination structure.
  • total int
    The total amount of pages. Default is the value WP_Query‘s max_num_pages or 1.
  • current int
    The current page number. Default is 'paged' query var or 1.
  • aria_current string
    The value for the aria-current attribute. Possible values are 'page', 'step', 'location', 'date', 'time', 'true', 'false'. Default is 'page'.
  • show_all bool
    Whether to show all pages. Default false.
  • end_size int
    How many numbers on either the start and the end list edges.
    Default 1.
  • mid_size int
    How many numbers to either side of the current pages. Default 2.
  • prev_next bool
    Whether to include the previous and next links in the list. Default true.
  • prev_text string
    The previous page text. Default ‘« Previous’.
  • next_text string
    The next page text. Default ‘Next »’.
  • type string
    Controls format of the returned value. Possible values are 'plain', 'array' and 'list'. Default is 'plain'.
  • add_args array
    An array of query args to add. Default false.
  • add_fragment string
    A string to append to each link.
  • before_page_number string
    A string to appear before the page number.
  • after_page_number string
    A string to append after the page number.

Default:''

Return

string|string[]|void String of page links or array of page links, depending on 'type' argument.
Void if total number of pages is less than 2.

Source

function paginate_links( $args = '' ) {
	global $wp_query, $wp_rewrite;

	// Setting up default values based on the current URL.
	$pagenum_link = html_entity_decode( get_pagenum_link() );
	$url_parts    = explode( '?', $pagenum_link );

	// Get max pages and current page out of the current query, if available.
	$total   = isset( $wp_query->max_num_pages ) ? $wp_query->max_num_pages : 1;
	$current = get_query_var( 'paged' ) ? (int) get_query_var( 'paged' ) : 1;

	// Append the format placeholder to the base URL.
	$pagenum_link = trailingslashit( $url_parts[0] ) . '%_%';

	// URL base depends on permalink settings.
	$format  = $wp_rewrite->using_index_permalinks() && ! strpos( $pagenum_link, 'index.php' ) ? 'index.php/' : '';
	$format .= $wp_rewrite->using_permalinks() ? user_trailingslashit( $wp_rewrite->pagination_base . '/%#%', 'paged' ) : '?paged=%#%';

	$defaults = array(
		'base'               => $pagenum_link, // http://example.com/all_posts.php%_% : %_% is replaced by format (below).
		'format'             => $format, // ?page=%#% : %#% is replaced by the page number.
		'total'              => $total,
		'current'            => $current,
		'aria_current'       => 'page',
		'show_all'           => false,
		'prev_next'          => true,
		'prev_text'          => __( '« Previous' ),
		'next_text'          => __( 'Next »' ),
		'end_size'           => 1,
		'mid_size'           => 2,
		'type'               => 'plain',
		'add_args'           => array(), // Array of query args to add.
		'add_fragment'       => '',
		'before_page_number' => '',
		'after_page_number'  => '',
	);

	$args = wp_parse_args( $args, $defaults );

	if ( ! is_array( $args['add_args'] ) ) {
		$args['add_args'] = array();
	}

	// Merge additional query vars found in the original URL into 'add_args' array.
	if ( isset( $url_parts[1] ) ) {
		// Find the format argument.
		$format       = explode( '?', str_replace( '%_%', $args['format'], $args['base'] ) );
		$format_query = isset( $format[1] ) ? $format[1] : '';
		wp_parse_str( $format_query, $format_args );

		// Find the query args of the requested URL.
		wp_parse_str( $url_parts[1], $url_query_args );

		// Remove the format argument from the array of query arguments, to avoid overwriting custom format.
		foreach ( $format_args as $format_arg => $format_arg_value ) {
			unset( $url_query_args[ $format_arg ] );
		}

		$args['add_args'] = array_merge( $args['add_args'], urlencode_deep( $url_query_args ) );
	}

	// Who knows what else people pass in $args.
	$total = (int) $args['total'];
	if ( $total < 2 ) {
		return;
	}
	$current  = (int) $args['current'];
	$end_size = (int) $args['end_size']; // Out of bounds? Make it the default.
	if ( $end_size < 1 ) {
		$end_size = 1;
	}
	$mid_size = (int) $args['mid_size'];
	if ( $mid_size < 0 ) {
		$mid_size = 2;
	}

	$add_args   = $args['add_args'];
	$r          = '';
	$page_links = array();
	$dots       = false;

	if ( $args['prev_next'] && $current && 1 < $current ) :
		$link = str_replace( '%_%', 2 == $current ? '' : $args['format'], $args['base'] );
		$link = str_replace( '%#%', $current - 1, $link );
		if ( $add_args ) {
			$link = add_query_arg( $add_args, $link );
		}
		$link .= $args['add_fragment'];

		$page_links[] = sprintf(
			'<a class="prev page-numbers" href="%s">%s</a>',
			/**
			 * Filters the paginated links for the given archive pages.
			 *
			 * @since 3.0.0
			 *
			 * @param string $link The paginated link URL.
			 */
			esc_url( apply_filters( 'paginate_links', $link ) ),
			$args['prev_text']
		);
	endif;

	for ( $n = 1; $n <= $total; $n++ ) :
		if ( $n == $current ) :
			$page_links[] = sprintf(
				'<span aria-current="%s" class="page-numbers current">%s</span>',
				esc_attr( $args['aria_current'] ),
				$args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
			);

			$dots = true;
		else :
			if ( $args['show_all'] || ( $n <= $end_size || ( $current && $n >= $current - $mid_size && $n <= $current + $mid_size ) || $n > $total - $end_size ) ) :
				$link = str_replace( '%_%', 1 == $n ? '' : $args['format'], $args['base'] );
				$link = str_replace( '%#%', $n, $link );
				if ( $add_args ) {
					$link = add_query_arg( $add_args, $link );
				}
				$link .= $args['add_fragment'];

				$page_links[] = sprintf(
					'<a class="page-numbers" href="%s">%s</a>',
					/** This filter is documented in wp-includes/general-template.php */
					esc_url( apply_filters( 'paginate_links', $link ) ),
					$args['before_page_number'] . number_format_i18n( $n ) . $args['after_page_number']
				);

				$dots = true;
			elseif ( $dots && ! $args['show_all'] ) :
				$page_links[] = '<span class="page-numbers dots">' . __( '&hellip;' ) . '</span>';

				$dots = false;
			endif;
		endif;
	endfor;

	if ( $args['prev_next'] && $current && $current < $total ) :
		$link = str_replace( '%_%', $args['format'], $args['base'] );
		$link = str_replace( '%#%', $current + 1, $link );
		if ( $add_args ) {
			$link = add_query_arg( $add_args, $link );
		}
		$link .= $args['add_fragment'];

		$page_links[] = sprintf(
			'<a class="next page-numbers" href="%s">%s</a>',
			/** This filter is documented in wp-includes/general-template.php */
			esc_url( apply_filters( 'paginate_links', $link ) ),
			$args['next_text']
		);
	endif;

	switch ( $args['type'] ) {
		case 'array':
			return $page_links;

		case 'list':
			$r .= "<ul class='page-numbers'>\n\t<li>";
			$r .= implode( "</li>\n\t<li>", $page_links );
			$r .= "</li>\n</ul>\n";
			break;

		default:
			$r = implode( "\n", $page_links );
			break;
	}

	/**
	 * Filters the HTML output of paginated links for archives.
	 *
	 * @since 5.7.0
	 *
	 * @param string $r    HTML output.
	 * @param array  $args An array of arguments. See paginate_links()
	 *                     for information on accepted arguments.
	 */
	$r = apply_filters( 'paginate_links_output', $r, $args );

	return $r;
}

Hooks

apply_filters( ‘paginate_links’, string $link )

Filters the paginated links for the given archive pages.

apply_filters( ‘paginate_links_output’, string $r, array $args )

Filters the HTML output of paginated links for archives.

Changelog

VersionDescription
4.9.0Added the aria_current argument.
2.1.0Introduced.

User Contributed Notes

  1. Skip to note 8 content

    Example With a Custom Query
    When querying a loop with new WP_Query set the total parameter to the max_num_pages property of the WP_Query object.

    Example of a custom query:

    <?php
    //Protect against arbitrary paged values
    $paged = ( get_query_var( 'paged' ) ) ? absint( get_query_var( 'paged' ) ) : 1;
    
    $args = array(
    	'posts_per_page' => 5,
    	'category_name' => 'gallery',
    	'paged' => $paged,
    );
    
    $the_query = new WP_Query( $args );
    ?>
    <!-- the loop etc.. -->

    Example of paginate_links parameters adapted to the custom query above:

    <?php
    $big = 999999999; // need an unlikely integer
    
    echo paginate_links( array(
    	'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    	'format' => '?paged=%#%',
    	'current' => max( 1, get_query_var('paged') ),
    	'total' => $the_query->max_num_pages
    ) );
    ?>
  2. Skip to note 9 content

    Improving Accessibility
    To add context to the numbered links to ensure that screen reader users understand what the links are for:

    <?php
    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>'
    ) );
    ?>
  3. Skip to note 10 content

    You might notice that paginate_links always gives back HTML, even if you’re setting type to array, which makes it a bit hard if you want to completely customise the HTML structure of your pagination. I wrote a little function that gives you back an array of objects instead of HTML that might make this a bit easier.

    function wpdocs_get_paginated_links( $query ) {
        // When we're on page 1, 'paged' is 0, but we're counting from 1,
        // so we're using max() to get 1 instead of 0
        $currentPage = max( 1, get_query_var( 'paged', 1 ) );
    
        // This creates an array with all available page numbers, if there
        // is only *one* page, max_num_pages will return 0, so here we also
        // use the max() function to make sure we'll always get 1
        $pages = range( 1, max( 1, $query->max_num_pages ) );
    
        // Now, map over $pages and return the page number, the url to that
        // page and a boolean indicating whether that number is the current page
        return array_map( function( $page ) use ( $currentPage ) {
            return ( object ) array(
                "isCurrent" => $page == $currentPage,
                "page" => $page,
                "url" => get_pagenum_link( $page )
            );
        }, $pages );
    }

    And use it like this (given that $query is the name of your custom query):

    <ul>
        <?php foreach( wpdocs_get_paginated_links( $query ) as $link ) : ?>
        <li>
            <?php if ( $link->isCurrent ): ?>
                <strong><?php _e( $link->page ) ?></strong>
            <?php else : ?>
                <a href="<?php esc_attr_e( $link->url ) ?>">
                    <?php _e( $link->page ) ?>
                </a>
            <?php endif; ?>
        </li>
        <?php endforeach; ?>
    </ul>
  4. Skip to note 12 content

    Note that the base argument should be fully qualified, e.g. https://example.com/my-post/%_%#some-id, not just %_%#some-id.

    If the base argument doesn’t contain the absolute URL, then when you are on the second page the link to the first page will not work; instead it will link to the current page. This is because the “Previous” link is set to the base argument with the %_% part removed, and no extra ?paged=%#% part added. Your browser then interprets this to mean the current page, so the “Previous” link points to the current page.

  5. Skip to note 13 content

    Basic Example
    To add pagination to your search results and archives, you can use the following example:

    <?php
    global $wp_query;
    
    $big = 999999999; // need an unlikely integer
    
    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
    ) );
    ?>
  6. Skip to note 14 content
    <?php
    global $wp_query;
    
    $big = 999999999; // need an unlikely integer
    
    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
    ) );
    ?>

    Aligned array items properly based on WPCS.

You must log in before being able to contribute a note or feedback.