WordPress plugins often need to display large datasets—orders, user submissions, or custom records—in the admin dashboard. Without wordPress admin table filters and search, users waste time scrolling through irrelevant data. By adding these features, you empower admins to:
In this guide, you’ll learn two methods to implement filters and search:

WP_List_Table handles pagination, sorting, and bulk actions out-of-the-box. Here’s how to extend it for filters and search.
if (!class_exists('WP_List_Table')) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
class Custom_Data_Table extends WP_List_Table {
// Define columns, prepare items, and more
}
public function prepare_items() {
global $wpdb;
// Define columns
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
// Fetch data
$search = isset($_REQUEST['s']) ? sanitize_text_field($_REQUEST['s']) : '';
$status_filter = isset($_REQUEST['status']) ? sanitize_text_field($_REQUEST['status']) : '';
$query = "SELECT * FROM {$wpdb->prefix}custom_table";
$where = array();
// Add search
if ($search) {
$where[] = $wpdb->prepare("(name LIKE %s OR email LIKE %s)", "%$search%", "%$search%");
}
// Add status filter
if ($status_filter) {
$where[] = $wpdb->prepare("status = %s", $status_filter);
}
if (!empty($where)) {
$query .= ' WHERE ' . implode(' AND ', $where);
}
// Pagination
$total_items = $wpdb->query($query);
$per_page = 20;
$current_page = $this->get_pagenum();
$this->set_pagination_args(array(
'total_items' => $total_items,
'per_page' => $per_page,
));
$query .= $wpdb->prepare(" LIMIT %d, %d", ($current_page - 1) * $per_page, $per_page);
$this->items = $wpdb->get_results($query, ARRAY_A);
}
Override extra_tablenav() to add dropdown filters:
public function extra_tablenav($which) {
if ($which === 'top') {
$statuses = array('pending', 'completed', 'cancelled');
echo '<div class="alignleft actions">';
echo '<select name="status" id="status-filter">';
echo '<option value="">All Statuses</option>';
foreach ($statuses as $status) {
$selected = isset($_REQUEST['status']) && $_REQUEST['status'] === $status ? 'selected' : '';
echo "<option value='$status' $selected>" . ucfirst($status) . "</option>";
}
echo '</select>';
submit_button('Filter', 'button', 'filter_action', false);
echo '</div>';
}
}
In your plugin’s admin page:
function display_custom_table() {
$table = new Custom_Data_Table();
$table->prepare_items();
echo '<form method="get">';
echo '<input type="hidden" name="page" value="your-plugin-page" />';
$table->search_box('Search', 'search_id');
$table->display();
echo '</form>';
}
For full control over design and functionality, build a table from scratch.
function display_custom_table() {
echo '<form method="get" action="">';
echo '<input type="hidden" name="page" value="your-plugin-page" />';
// Search Box
echo '<p class="search-box">';
echo '<input type="search" name="s" value="' . esc_attr(isset($_GET['s']) ? $_GET['s'] : '') . '" />';
submit_button('Search', 'button', false, false);
echo '</p>';
// Status Filter
echo '<select name="status">';
echo '<option value="">All Statuses</option>';
foreach (array('pending', 'completed') as $status) {
$selected = isset($_GET['status']) && $_GET['status'] === $status ? 'selected' : '';
echo "<option value='$status' $selected>" . ucfirst($status) . "</option>";
}
echo '</select>';
submit_button('Filter', 'button', false, false);
// Display table
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr><th>ID</th><th>Name</th><th>Status</th></tr></thead>';
echo '<tbody>';
// Populate data here
echo '</tbody></table>';
// Pagination
echo '<div class="tablenav bottom">';
echo '<div class="tablenav-pages">';
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => __('« Previous'),
'next_text' => __('Next »'),
'total' => $total_pages,
'current' => $current_page,
));
echo '</div></div>';
echo '</form>';
}
global $wpdb;
$per_page = 20;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
$where = array();
$params = array();
// Handle search
if (!empty($_GET['s'])) {
$search = sanitize_text_field($_GET['s']);
$where[] = "(name LIKE %s OR email LIKE %s)";
$params[] = "%$search%";
$params[] = "%$search%";
}
// Handle status filter
if (!empty($_GET['status'])) {
$status = sanitize_text_field($_GET['status']);
$where[] = "status = %s";
$params[] = $status;
}
// Build query
$query = "SELECT * FROM {$wpdb->prefix}custom_table";
if (!empty($where)) {
$query .= ' WHERE ' . implode(' AND ', $where);
}
$query .= $wpdb->prepare(" LIMIT %d OFFSET %d", $per_page, $offset);
$results = $wpdb->get_results($query, ARRAY_A);
foreach ($results as $row) {
echo "<tr>";
echo "<td>{$row['id']}</td>";
echo "<td>{$row['name']}</td>";
echo "<td>{$row['status']}</td>";
echo "</tr>";
}
When generating pagination links, include current filter/search values:
$pagination_args = array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '« Previous',
'next_text' => 'Next »',
'total' => $total_pages,
'current' => $current_page,
'add_args' => array(
's' => isset($_GET['s']) ? $_GET['s'] : '',
'status' => isset($_GET['status']) ? $_GET['status'] : '',
),
);
echo paginate_links($pagination_args);
Integrating filters and search into your plugin’s admin table revolutionizes how users manage data, turning overwhelming datasets into streamlined workflows. Whether opting for the built-in efficiency of WP_List_Table or crafting a custom HTML table for tailored designs, the core principles remain consistent: securely capturing user inputs, dynamically adjusting database queries based on those inputs, and ensuring pagination retains applied filters. These steps not only enhance functionality but also maintain a seamless experience as users navigate through pages of results.
For those looking to deepen their expertise, resources like WordPress Codex WP_List_Table documentation or SQL optimization guides offer valuable insights. By mastering these techniques, you’ll deliver an intuitive and powerful admin interface, setting your plugin apart in the competitive WordPress ecosystem.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developer understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.