WP_List_Table API
WP_List_Table is the base class for displaying data in admin tables (posts, users, comments, etc.). While marked for internal use, it’s widely used by plugin developers.
Class Overview
File: wp-admin/includes/class-wp-list-table.php
The class provides:
- Pagination
- Bulk actions
- Column sorting
- Search functionality
- Screen options integration
Creating a Custom List Table
Step 1: Extend the Class
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
class My_Custom_List_Table extends WP_List_Table {
public function __construct() {
parent::__construct( array(
'singular' => 'item', // Singular label
'plural' => 'items', // Plural label
'ajax' => false, // Enable AJAX
'screen' => null, // Screen ID (auto-detected)
) );
}
}
Step 2: Define Columns
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />', // Checkbox column
'title' => __( 'Title' ),
'author' => __( 'Author' ),
'date' => __( 'Date' ),
'status' => __( 'Status' ),
);
}
Step 3: Define Sortable Columns
protected function get_sortable_columns() {
return array(
'title' => array( 'title', false ), // false = asc by default
'date' => array( 'date', true ), // true = desc by default
);
}
Step 4: Prepare Items
public function prepare_items() {
// Set column headers
$this->_column_headers = array(
$this->get_columns(),
array(), // Hidden columns
$this->get_sortable_columns(),
);
// Process bulk actions
$this->process_bulk_action();
// Get data
$data = $this->get_data();
// Handle sorting
$orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'title';
$order = isset( $_GET['order'] ) ? sanitize_key( $_GET['order'] ) : 'asc';
usort( $data, function( $a, $b ) use ( $orderby, $order ) {
$result = strcmp( $a[ $orderby ], $b[ $orderby ] );
return ( $order === 'asc' ) ? $result : -$result;
} );
// Pagination
$per_page = $this->get_items_per_page( 'items_per_page', 20 );
$current_page = $this->get_pagenum();
$total_items = count( $data );
$this->items = array_slice( $data, ( $current_page - 1 ) * $per_page, $per_page );
$this->set_pagination_args( array(
'total_items' => $total_items,
'per_page' => $per_page,
'total_pages' => ceil( $total_items / $per_page ),
) );
}
Step 5: Display Column Content
// Default column handler
protected function column_default( $item, $column_name ) {
return isset( $item[ $column_name ] ) ? esc_html( $item[ $column_name ] ) : '';
}
// Checkbox column
protected function column_cb( $item ) {
return sprintf(
'<input type="checkbox" name="items[]" value="%d" />',
absint( $item['id'] )
);
}
// Title column with row actions
protected function column_title( $item ) {
$actions = array(
'edit' => sprintf(
'<a href="%s">%s</a>',
admin_url( 'admin.php?page=my-page&action=edit&id=' . absint( $item['id'] ) ),
__( 'Edit' )
),
'delete' => sprintf(
'<a href="%s" onclick="return confirm('Are you sure?')">%s</a>',
wp_nonce_url(
admin_url( 'admin.php?page=my-page&action=delete&id=' . absint( $item['id'] ) ),
'delete_item_' . $item['id']
),
__( 'Delete' )
),
);
return sprintf(
'<strong><a href="%s">%s</a></strong>%s',
admin_url( 'admin.php?page=my-page&action=edit&id=' . absint( $item['id'] ) ),
esc_html( $item['title'] ),
$this->row_actions( $actions )
);
}
Step 6: Define Bulk Actions
protected function get_bulk_actions() {
return array(
'delete' => __( 'Delete' ),
'export' => __( 'Export' ),
);
}
public function process_bulk_action() {
if ( 'delete' === $this->current_action() ) {
// Verify nonce
check_admin_referer( 'bulk-items' );
$items = isset( $_GET['items'] ) ? array_map( 'absint', $_GET['items'] ) : array();
foreach ( $items as $item_id ) {
// Delete item
$this->delete_item( $item_id );
}
}
}
Advanced Features
Views/Filters
protected function get_views() {
$current = isset( $_GET['status'] ) ? sanitize_key( $_GET['status'] ) : 'all';
$views = array(
'all' => sprintf(
'<a href="%s" class="%s">%s <span class="count">(%d)</span></a>',
admin_url( 'admin.php?page=my-page' ),
$current === 'all' ? 'current' : '',
__( 'All' ),
$this->count_items()
),
'published' => sprintf(
'<a href="%s" class="%s">%s <span class="count">(%d)</span></a>',
admin_url( 'admin.php?page=my-page&status=published' ),
$current === 'published' ? 'current' : '',
__( 'Published' ),
$this->count_items( 'published' )
),
'draft' => sprintf(
'<a href="%s" class="%s">%s <span class="count">(%d)</span></a>',
admin_url( 'admin.php?page=my-page&status=draft' ),
$current === 'draft' ? 'current' : '',
__( 'Draft' ),
$this->count_items( 'draft' )
),
);
return $views;
}
Search Box
// In prepare_items()
$search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : '';
if ( ! empty( $search ) ) {
$data = array_filter( $data, function( $item ) use ( $search ) {
return stripos( $item['title'], $search ) !== false;
} );
}
Extra Table Navigation
protected function extra_tablenav( $which ) {
if ( $which === 'top' ) {
?>
<div class="alignleft actions">
<select name="filter_category">
<option value="">All Categories</option>
<option value="cat1">Category 1</option>
<option value="cat2">Category 2</option>
</select>
<?php submit_button( __( 'Filter' ), '', 'filter_action', false ); ?>
</div>
<?php
}
}
No Items Message
public function no_items() {
_e( 'No items found.' );
}
Display the Table
add_action( 'admin_menu', 'register_my_list_page' );
function register_my_list_page() {
$hook = add_menu_page(
'My Items',
'My Items',
'manage_options',
'my-items',
'render_my_list_page'
);
// Add screen options
add_action( "load-$hook", 'add_my_list_screen_options' );
}
function add_my_list_screen_options() {
add_screen_option( 'per_page', array(
'label' => __( 'Items per page' ),
'default' => 20,
'option' => 'items_per_page',
) );
// Instantiate table (required for screen options)
$GLOBALS['my_list_table'] = new My_Custom_List_Table();
}
function render_my_list_page() {
$table = $GLOBALS['my_list_table'];
$table->prepare_items();
?>
<div class="wrap">
<h1 class="wp-heading-inline"><?php _e( 'My Items' ); ?></h1>
<a href="<?php echo admin_url( 'admin.php?page=my-items&action=new' ); ?>" class="page-title-action">
<?php _e( 'Add New' ); ?>
</a>
<hr class="wp-header-end">
<form method="get">
<input type="hidden" name="page" value="my-items">
<?php
$table->views();
$table->search_box( __( 'Search Items' ), 'search' );
$table->display();
?>
</form>
</div>
<?php
}
Key Methods Reference
| Method | Description |
|---|---|
get_columns() |
Define table columns |
get_sortable_columns() |
Define sortable columns |
prepare_items() |
Prepare data for display |
column_default() |
Default column output |
column_{name}() |
Custom output for specific column |
get_bulk_actions() |
Define bulk actions |
current_action() |
Get current bulk action |
process_bulk_action() |
Handle bulk action |
get_views() |
Define filter views |
extra_tablenav() |
Extra controls in tablenav |
row_actions() |
Display row actions |
display() |
Render the table |
search_box() |
Display search box |
get_pagenum() |
Current page number |
get_items_per_page() |
Items per page setting |
set_pagination_args() |
Set pagination parameters |
Built-in List Table Classes
WordPress includes these specialized list tables:
WP_Posts_List_Table– Posts/PagesWP_Comments_List_Table– CommentsWP_Users_List_Table– UsersWP_Media_List_Table– Media LibraryWP_Terms_List_Table– Categories/TagsWP_Plugins_List_Table– PluginsWP_Themes_List_Table– ThemesWP_Links_List_Table– Links (deprecated)
Use _get_list_table() to get an instance:
$table = _get_list_table( 'WP_Posts_List_Table' );
Filters
| Filter | Description |
|---|---|
manage_{screen_id}_columns |
Modify column headers |
manage_{post_type}_posts_columns |
Modify post type columns |
{post_type}_row_actions |
Modify row actions for post type |
list_table_primary_column |
Set primary column |
default_hidden_columns |
Set default hidden columns |