Add WordPress pagination on custom options pages
Published: – Leave a comment
Post types usually already have a nice list view (if set to public) in the backend to list its posts. Unfortunately, to customize it you would need some effort in using multiple filters to change the columns, the column heads, the sorting and adjust the query to your liking.
Today, I show another approach by just adding a custom page with a similar but custom pagination.
The following example only shows the pagination itself, no custom filter function or similar. To add this, a little bit more effort is required. But to achieve a custom page with pagination, you don’t need.
Getting the pagination
First of all, you need to get the pagination. You can use the following function to do that:
/**
* Get the pagination.
*
* @param int $page_count Count of pages
* @param int $license_count Count of licenses
* @param int $current_page Current page number
* @param string $display Where to display the navigation
*/
function get_paged_navigation( int $page_count, int $license_count, int $current_page, string $display = 'top' ): void {
if ( $page_count <= 1 ) {
return;
}
?>
<div class="tablenav top">
<div class="tablenav-pages">
<span class="displaying-num">
<?php
/* translators: entry count */
printf( esc_html__( '%d entries', 'my-textdomain' ), esc_html( $license_count ) );
?>
</span>
<?php if ( $current_page !== 1 ) : ?>
<a class="first-page button" href="<?php echo esc_url( admin_url( '/tools.php?page=my-page' ) ); ?>"><span class="screen-reader-text"><?php esc_html_e( 'First page', 'my-textdomain' ); ?></span><span aria-hidden="true">«</span></a>
<?php else : ?>
<span class="pagination-links"><span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>
<?php endif; ?>
<?php if ( $current_page - 1 > 0 ) : ?>
<a class="prev-page button" href="<?php echo esc_url( admin_url( '/tools.php?page=my-page' ) . '&paged=' . ( $current_page - 1 ) ); ?>"><span class="screen-reader-text"><?php esc_html_e( 'Previous page', 'my-textdomain' ); ?></span><span aria-hidden="true">‹</span></a>
<?php else : ?>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">‹</span>
<?php endif; ?>
<?php if ( $display === 'top' ) : ?>
<span class="paging-input"><label for="current-page-selector" class="screen-reader-text"><?php esc_html_e( 'Current page', 'my-textdomain' ); ?>></label><input class="current-page" id="current-page-selector" type="text" name="paged" value="<?php echo esc_attr( $current_page ); ?>" size="1" aria-describedby="table-paging"><span class="tablenav-paging-text"> <?php esc_html_e( 'of', 'my-textdomain' ); ?> <span class="total-pages"><?php echo esc_html( $page_count ); ?></span></span></span>
<?php else : ?>
<span class="paging-input"><label for="current-page-selector" class="screen-reader-text"><?php esc_html_e( 'Current page', 'my-textdomain' ); ?>></label><?php echo esc_html( $current_page ); ?> <?php esc_html_e( 'of', 'my-textdomain' ); ?> <span class="total-pages"><?php echo esc_html( $page_count ); ?></span></span></span>
<?php endif; ?>
<?php if ( $current_page + 1 <= $page_count ) : ?>
<a class="next-page button" href="<?php echo esc_url( admin_url( '/tools.php?page=my-page' ) . '&paged=' . ( $current_page + 1 ) ); ?>"><span class="screen-reader-text"><?php esc_html_e( 'Next page', 'my-textdomain' ); ?></span><span aria-hidden="true">›</span></a>
<?php else : ?>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">›</span>
<?php endif; ?>
<?php if ( $current_page + 1 <= $page_count ) : ?>
<a class="last-page button" href="<?php echo esc_url( admin_url( '/tools.php?page=my-page' ) . '&paged=' . (int) $page_count ); ?>"><span class="screen-reader-text"><?php esc_html_e( 'Last page', 'my-textdomain' ); ?></span><span aria-hidden="true">»</span></a></span>
<?php else : ?>
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">»</span>
<?php endif; ?>
</div>
</div>
<?php
}
Code language: PHP (php)
Add pagination to options page
On your options page callback, you need to call this function in order to get the navigation on the correct page:
/**
* Add sub menu item in options menu.
*/
function my_options_page(): void {
add_submenu_page(
'tools.php',
__( 'My Page', 'my-textdomain' ),
__( 'My Page', 'my-textdomain' ),
'manage_options',
'my-textdomain',
'my_options_page_html'
);
}
add_action( 'admin_menu', 'my_options_page' );
/**
* Sub menu item:
* callback functions
*/
function my_options_page_html(): void {
// check user capabilities
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( is_main_site() ) {
$filter = 'all';
}
else {
$filter = 'current';
}
$current_page = isset( $_GET['paged'] ) ? (int) sanitize_text_field( wp_unslash( $_GET['paged'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$licenses = array_reverse( [] ); // TODO: get your data
$license_count = count( $licenses );
$page_item_limit = 20;
$page_count = ceil( $license_count / $page_item_limit );
// check for abusing parameter
if ( $current_page > $page_count ) {
$current_page = $page_count;
}
else if ( $current_page < 1 ) {
$current_page = 1;
}
?>
<h1><?php esc_html_e( 'My Page', 'my-textdomain' ); ?></h1>
<form id="posts-filter" method="get">
<?php settings_fields( 'my_page' ); ?>
<input type="hidden" name="page" value="my-page" />
<div class="wrap">
<?php get_paged_navigation( $page_count, $license_count, $current_page ); ?>
<table class="wp-list-table widefat fixed striped table-view-list">
<thead>
<tr>
<th scope="col" id="image" class="manage-column column-image column-primary"><span><?php esc_html_e( 'Image', 'my-textdomain' ); ?></span></th>
<th scope="col" id="author" class="manage-column column-author"><?php esc_html_e( 'Licensed by', 'my-textdomain' ); ?></th>
<th scope="col" id="site" class="manage-column column-site"><?php esc_html_e( 'Site', 'my-textdomain' ); ?></th>
<th scope="col" id="date" class="manage-column column-date"><?php esc_html_e( 'Licensing Date', 'my-textdomain' ); ?></th>
</tr>
</thead>
<tbody id="the-list">
<?php if ( empty( $licenses ) ) : ?>
<tr class="no-items"><td class="colspanchange" colspan="4"><?php esc_html_e( 'No items found.', 'my-textdomain' ); ?></td></tr>
<?php
else :
$count = 0;
foreach ( $licenses as $license ) :
$count++;
if (
$page_count > 1
&& (
// item is on next page
$count > $page_item_limit * $current_page
// item is on previous page
|| $count <= $page_item_limit * ( $current_page - 1 )
)
) {
continue;
}
$timestamp = ! empty( $license['date'] ) ? strtotime( $license['date'] ) : 0;
$date = ( $timestamp ? wp_date( get_option( 'date_format' ), $timestamp ) . ' ' . __( 'at', 'my-textdomain' ) . ' ' . wp_date( get_option( 'time_format' ), $timestamp ) : __( 'Unknown', 'my-textdomain' ) );
?>
<tr id="font-<?php echo esc_attr( $license['image_id'] ); ?>" class="iedit author-self level-0 font-<?php echo esc_attr( $license['image_id'] ); ?> type-page status-publish hentry">
<td class="title column-image column-primary" data-colname="<?php esc_html_e( 'Image', 'my-textdomain' ); ?>">
<img src="<?php echo esc_url( $license['thumbnail_url'] ); ?>" alt="" loading="lazy" style="max-width: 250px;" /><br />
<strong>
<?php
/* translators: the image ID */
printf( esc_html__( 'Image ID: %d', 'my-textdomain' ), esc_html( (int) $license['image_id'] ) );
?>
</strong>
</td>
<td class="author column-author" data-colname="<?php esc_html_e( 'Author', 'my-textdomain' ); ?>"><?php echo ( isset( $license['user_id'] ) ? esc_html( get_userdata( $license['user_id'] )->display_name ) : esc_html__( 'Unknown', 'my-textdomain' ) ); ?></td>
<td class="site column-site" data-colname="<?php esc_html_e( 'Site', 'my-textdomain' ); ?>"><?php echo ( ! empty( $license['site_id'] ) ? '<a href="' . esc_url( get_site_url( $license['site_id'], '/wp-admin/' ) ) . '">' . esc_url( get_site_url( $license['site_id'] ) ) . '</a>' : esc_html__( 'Unknown', 'my-textdomain' ) ); ?></td>
<td class="date column-date" data-colname="<?php esc_html_e( 'Date', 'my-textdomain' ); ?>"><?php echo esc_html( $date ); ?></td>
</tr>
<?php
endforeach;
endif;
?>
</tbody>
<tfoot>
<tr>
<th scope="col" id="image" class="manage-column column-image column-primary"><span><?php esc_html_e( 'Image', 'my-textdomain' ); ?></span></th>
<th scope="col" id="author" class="manage-column column-author"><?php esc_html_e( 'Licensed by', 'my-textdomain' ); ?></th>
<th scope="col" id="site" class="manage-column column-site"><?php esc_html_e( 'Site', 'my-textdomain' ); ?></th>
<th scope="col" id="date" class="manage-column column-date"><?php esc_html_e( 'Licensing Date', 'my-textdomain' ); ?></th>
</tr>
</tfoot>
</table>
<?php get_paged_navigation( $page_count, $license_count, $current_page, 'bottom' ); ?>
</div>
</form>
<?php
}
Code language: PHP (php)
In my case, I registered the options page as submenu page of the tools menu. The pagination is output on top of and below the table, where only the top one has an input field to directly jump to a specific page number.
What you now still need is getting your data in line 36 and adjust the table to your liking. In my case, it outputs a list of image licenses from Adobe Stock, which looks like this: