Get 50% Discount Offer 26 Days

Recommended Services
Supported Scripts
WordPress
Hubspot
Joomla
Drupal
Wix
Shopify
Magento
Typeo3
WordPress Admin Table Filters and Search with WP_List_Table Explained

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:

  • Find specific entries instantly (e.g., orders by status).
  • Filter data by date, category, or custom criteria.
  • Improve workflow efficiency with paginated results.

In this guide, you’ll learn two methods to implement filters and search:

  1. Using WordPress built-in WP_List_Table class (recommended for standard tables).
  2. Building a custom HTML table (for advanced layouts).

Requirements

  • Basic knowledge of WordPress plugin development.
  • Familiarity with PHP, SQL, and WordPress hooks.
  • Access to a custom database table (e.g., created via dbDelta()).
WordPress Admin Table Filters and Search

Method 1: Using WP_List_Table (Beginner-Friendly)

WP_List_Table handles pagination, sorting, and bulk actions out-of-the-box. Here’s how to extend it for filters and search.

Step 1: Create a Custom Class Extending WP_List_Table

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

}

Step 2: Define Columns and Data Preparation

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);

}

Step 3: Add Filters and Search Box

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>';

    }

}

Step 4: Display the Table

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>';

}

Method 2: Custom HTML Table (Advanced)

For full control over design and functionality, build a table from scratch.

Step 1: Create the HTML Form and Table Structure

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>';

}

Step 2: Query Data Based on Filters

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);

Step 3: Populate Table Data

foreach ($results as $row) {

    echo "<tr>";

    echo "<td>{$row['id']}</td>";

    echo "<td>{$row['name']}</td>";

    echo "<td>{$row['status']}</td>";

    echo "</tr>";

}

Security Best Practices

  1. Sanitize Inputs: Always sanitize $_GET/$_REQUEST values with sanitize_text_field().
  2. Use Nonces: Add nonces to forms to prevent CSRF attacks.
  3. Escape Outputs: Use esc_html() or esc_attr() when rendering data.

Pagination with Search Parameters

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);

Frequently Asked Questions

Add two date inputs (date_from and date_to), then modify your SQL query:

if (!empty($_GET['date_from'])) {

    $where[] = "date >= %s";

    $params[] = sanitize_text_field($_GET['date_from']);

}

Yes! Use JavaScript to fetch data via the REST API or admin-ajax.php when filters change.

Add CSS classes like widefat and striped for WordPress-native styling or enqueue a custom CSS file.

Ensure add_args in paginate_links() includes all filter parameters.

Add indexes to filtered columns (e.g., status), cache results, or limit searchable columns.

Conclusion

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.

About the writer

Hassan Tahir Author

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Lifetime Solutions:

VPS SSD

Lifetime Hosting

Lifetime Dedicated Servers