{"id":168226,"date":"2017-10-11T13:00:34","date_gmt":"2017-10-11T13:00:34","guid":{"rendered":"https:\/\/premium.wpmudev.org\/blog\/?p=168226"},"modified":"2022-03-02T04:36:56","modified_gmt":"2022-03-02T04:36:56","slug":"wordpress-admin-tables","status":"publish","type":"post","link":"https:\/\/wpmudev.com\/blog\/wordpress-admin-tables\/","title":{"rendered":"Adding WordPress Admin Tables to Plugins with WP_List_Table"},"content":{"rendered":"<p>WordPress administration tables, or list tables, are used extensively in admin areas to list posts, pages, users, etc.<\/p>\n<p>If you\u2019ve ever wanted to add such list tables with your own data to your plugin, WordPress provides a nifty way to do so with the WP_List_Table class.<\/p>\n<p>In this article, I&#8217;ll show you how to use the WordPress API to add WordPress-like administration tables or list tables to your plugin&#8217;s admin screen.<\/p>\n<p>Continue reading, or jump ahead using the links below:<\/p>\n<ul>\n<li><a href=\"#background\">WordPress Admin Tables \u2013 The Background<\/a>\n<ul>\n<li><a href=\"#when-to-use\">When to Use the WordPress Admin Table<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#anatomy\">Anatomy of the WP_List_Table Class<\/a><\/li>\n<li><a href=\"#list-table-plugin\">The Custom List Table Plugin for User Meta Operations<\/a><\/li>\n<li><a href=\"#inheriting\">Inheriting the WP_List_Table Class<\/a><\/li>\n<li><a href=\"#rendering\">Rendering a List Table<\/a>\n<ul>\n<li><a href=\"#adding-page\">Adding the Plugin Menu Page<\/a><\/li>\n<li><a href=\"#verifying-setup\">Verifying the Barebone List Table Setup<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#overriding-methods\">Overriding Methods of the WP_List_Table Class<\/a>\n<ul>\n<li><a href=\"#get_columns\">WP_List_Table::get_columns()<\/a><\/li>\n<li><a href=\"#prepare_items\">WP_List_Table::prepare_items()<\/a><\/li>\n<li><a href=\"#column_default\">WP_List_Table::column_default()<\/a><\/li>\n<li><a href=\"#column_cb\">WP_List_Table::column_cb()<\/a><\/li>\n<li><a href=\"#get_sortable_columns\">WP_List_Table::get_sortable_columns()<\/a><\/li>\n<li><a href=\"#set_pagination_args\">WP_List_Table::set_pagination_args()<\/a><\/li>\n<li><a href=\"#search_box\">WP_List_Table::search_box()<\/a><\/li>\n<li><a href=\"#get_bulk_actions\">WP_List_Table::get_bulk_actions()<\/a><\/li>\n<li><a href=\"#adding-actions\">Adding Row Action Links to the List Table<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>The WordPress list tables are used almost everywhere in the dashboard. You&#8217;ll find them used to display posts, pages, users, comments, etc. as well as in many popular plugins. Over the years, they&#8217;ve become the de facto standard for displaying tabular information in WordPress.<\/p>\n<p>For this article, I&#8217;ve built a custom Admin Table with the help of a plugin that uses object-oriented constructs. The plugin <a href=\"https:\/\/github.com\/nuancedesignstudio\/nds-wp-list-table-demo\" rel=\"noopener\" target=\"_blank\">can be downloaded from here<\/a> to follow along with the article. But before we get into that, let&#8217;s understand what Admin Tables really do.<\/p>\n<p>I&#8217;ll also be using the terms <code>Admin Tables<\/code> and <code>List Tables<\/code> interchangeably for the rest of the article.<\/p>\n<p><em>Note: This article is for intermediate-advanced WordPress developers. You will require a working knowledge of WordPress, PHP and the <a href=\"https:\/\/developer.wordpress.org\/plugins\/\" target=\"_blank\">WordPress Plugin API<\/a> before going ahead. If you&#8217;re unsure or would like a refresher I recommend you read through the following:<\/em><\/p>\n<ul>\n<li><a href=\"https:\/\/wpmudev.com\/blog\/wordpress-development-beginners-getting-started\/\" target=\"_blank\">WordPress Development for Beginners: Getting Started<\/a><\/li>\n<li><a href=\"https:\/\/wpmudev.com\/blog\/wordpress-development-beginners-widgets-menus\/\" target=\"_blank\">WordPress Development for Beginners: Widgets and Menus<\/a><\/li>\n<li><a href=\"https:\/\/wpmudev.com\/blog\/wordpress-development-beginners-building-plugins\/\" target=\"_blank\">WordPress Development for Beginners: Building Plugins<\/a><\/li>\n<li><a href=\"https:\/\/wpmudev.com\/blog\/advanced-wordpress-development-introduction-to-oop\" target=\"_blank\">Advanced WordPress Development: Introduction to Object-Oriented Programming<\/a><\/li>\n<\/ul>\n<h2 id=\"background\">WordPress Admin Tables \u2013 The Background<\/h2>\n<p>You&#8217;ve already interacted with an Admin Table while accessing pages like Posts and Users in the Dashboard. A WordPress Administration Table is implemented using the <code><a href=\"https:\/\/core.trac.wordpress.org\/browser\/tags\/4.8\/src\/\/wp-admin\/includes\/class-wp-list-table.php#L0\" rel=\"noopener\" target=\"_blank\">WP_List_Table<\/a><\/code> class located at <code>\/wp-admin\/includes\/class-wp-list-table.php<\/code>. It provides the necessary framework to display information in a tabular fashion as well as manipulate data intuitively. Here&#8217;s an overview of what a typical WordPress Admin Table entails:<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/09\/wordpress-admin-table.png\" alt=\"wordpress admin table\" width=\"600\" height=\"591\" \/><figcaption class=\"wp-caption-text\">A WordPress Admin Table<\/figcaption><\/figure>\n<\/div>\n<p>You&#8217;ll notice the familiar bulk actions, row actions with hover links, pagination and screen options, all of which have been an integral part of WordPress ever since.<\/p>\n<p>While <code>Screen Options<\/code> are not directly part of <code>WP_List_Table<\/code>, they work very closely with it. They allow control over the visibility of columns as well as the pagination by limiting the information displayed on the page.<\/p>\n<h3 id=\"when-to-use\">When to Use the WordPress Admin Table<\/h3>\n<p>Just because the WordPress API provides a way to build native List Tables doesn&#8217;t mean one must use it. In my opinion, a big factor in deciding to use the WordPress provided Admin Tables should be <a href=\"https:\/\/wpmudev.com\/blog\/ux-ui-wordpress\/\" target=\"_blank\" rel=\"noopener\">User Experience<\/a>. With the <code>WP_List_Table<\/code> class, WordPress takes care of styling the table UI for you. The familiarity of working with Admin Tables (all over WordPress) and the native look-and-feel are elements that will certainly help users interact with your table data in a seamless manner.<\/p>\n<h2 id=\"anatomy\">Anatomy of the WP_List_Table Class<\/h2>\n<p>Since its inception in version 3.1.0 of WordPress, the WP_List_Table class has increasingly grown in popularity among developers. The <a href=\"https:\/\/developer.wordpress.org\/reference\/classes\/wp_list_table\/\" rel=\"noopener\" target=\"_blank\">WordPress Codex class reference for the WP_List_Table<\/a> provides comprehensive information about its methods, properties and usage.<\/p>\n<p>The infographic below outlines the building blocks of an Admin Table from the class reference.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/anatomy-wp-list-table-class-600.png\" alt=\"anatomy-wp-list-table-class\" width=\"450\" height=\"600\" \/><figcaption class=\"wp-caption-text\">Building blocks of the WP_List_Table<\/figcaption><\/figure>\n<\/div>\n<p>A more complex Admin Table would also feature additional drop-down filters and action links above the table such as those used to show &#8220;Published Posts&#8221; and &#8220;Drafts&#8221;.<\/p>\n<p>Now that we have a fair understanding of an Admin Table and the <code>WP_List_Table<\/code> class, let&#8217;s put this knowledge to use with the help of a custom plugin.<\/p>\n<h2 id=\"list-table-plugin\">The Custom List Table Plugin for User Meta Operations<\/h2>\n<p>Before I get into the implementation details of List Tables, I want to briefly touch upon the custom plugin that I&#8217;ve prepared for this article. You can <a href=\"https:\/\/github.com\/nuancedesignstudio\/nds-wp-list-table-demo\" rel=\"noopener\" target=\"_blank\">download it from here<\/a> to follow along. I recommend that you have it opened in a suitable editor and install it on a <strong>local development WordPress setup only<\/strong>.<\/p>\n<p>The plugin is based on <a href=\"https:\/\/github.com\/nuancedesignstudio\/WordPress-Plugin-Boilerplate\" rel=\"noopener\" target=\"_blank\">my own plugin template<\/a> which is a fork of the original <a href=\"http:\/\/wppb.io\/\" rel=\"noopener\" target=\"_blank\">WordPress Plugin Boilerplate<\/a> project. It&#8217;s similar to the original project in many aspects but also has support for namespaces and autoloading. This way, I don&#8217;t need to have unique prefixes for every class or function, and don&#8217;t end up with a lot of <code>include<\/code> and <code>require<\/code> statements. However, the minimum required PHP version for my plugin is 5.6.0.<\/p>\n<p>We&#8217;ll mostly be working with files in the following directories of the plugin:<\/p>\n<ul>\n<li><code>inc\/core\/*<\/code> &#8211; for core functionality of the plugin<\/li>\n<li><code>inc\/admin\/*<\/code> &#8211; for displaying the List Table in the admin area<\/li>\n<li><code>inc\/libraries\/*<\/code> &#8211; libraries for the plugin<\/li>\n<\/ul>\n<p>Feel free to structure the plugin based on your own coding and layout preferences. I prefer to use <a href=\"https:\/\/developer.wordpress.org\/plugins\/the-basics\/best-practices\/#boilerplate-starting-points\" rel=\"noopener\" target=\"_blank\">Boilerplate Starting Points<\/a> as they&#8217;re among the many best practices listed in the WordPress Plugin Handbook.<\/p>\n<h3>Purpose<\/h3>\n<p>The custom plugin displays all registered WordPress users in its own Admin Table with control over screen options, pagination and row action links to perform operations on the user&#8217;s metadata. Each row action leads to a plugin page to carry out these operations.<\/p>\n<p>This use-case certainly isn&#8217;t the ideal one as I could simply have added the functionality to the default WordPress Users page using <code><a href=\"https:\/\/developer.wordpress.org\/reference\/hooks\/user_row_actions\/\" rel=\"noopener\" target=\"_blank\">user_row_actions<\/a><\/code>; however, it serves the purpose of implementing a List Table from scratch. So let&#8217;s get started.<\/p>\n<h2 id=\"inheriting\">Inheriting the WP_List_Table Class<\/h2>\n<p>To create an Admin Table you will need to define a child class in your plugin that extends the core <code>WP_List_Table<\/code> class provided by WordPress. So, among the first things that you should do, is copy the file <code>class-wp-list-table.php<\/code> under <code>\/wp-admin\/includes\/<\/code> to your plugin. I strongly recommend this because the core class has been <a href=\"https:\/\/developer.wordpress.org\/reference\/classes\/wp_list_table\/\" rel=\"noopener\" target=\"_blank\">marked as private<\/a>, and so, any potential changes in the class or its methods by WordPress will not break your plugin.<\/p>\n<p>In my plugin, I&#8217;ve copied the file to <code>inc\/libraries<\/code> and added a <code>Namespace<\/code> directive. If you&#8217;re not using Namespaces, you will need to<a href=\"https:\/\/developer.wordpress.org\/plugins\/the-basics\/best-practices\/#prefix-everything\" rel=\"noopener\" target=\"_blank\"> prefix everything<\/a>.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/importing-wp-list-table-class.png\" alt=\"importing the wp-list-table class manually\" width=\"600\" height=\"271\" \/><figcaption class=\"wp-caption-text\">Copying the wp-list-table class file<\/figcaption><\/figure>\n<\/div>\n<p>I then defined my own PHP child class <code>User_List_Table<\/code> in <code>inc\/admin\/class-user-list-table.php<\/code> that extended the base <code>WP_List_Table<\/code> class under the folder <code>inc\/libraries<\/code> of my plugin.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"f91ab38c4aac062350d9477936e9a514\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/f91ab38c4aac062350d9477936e9a514.js?file=class+user+list+table\">Loading gist f91ab38c4aac062350d9477936e9a514<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>The snippet above is a barebone implementation with two methods that must be overridden. Obviously, there&#8217;s a lot that needs to be done in the child class but I wanted to start off by just rendering an empty List Table first.<\/p>\n<h2 id=\"rendering\">Rendering a List Table<\/h2>\n<p>At this stage, you may choose to add the constructor and other methods to the child class; however, I prefer to render an empty List Table first. I find this top-down approach rather effective as I can see the output of the List Table at each stage, making it easier to iron out any bugs that may creep in.<\/p>\n<p>Rendering a List Table typically requires three steps:<\/p>\n<ul>\n<li>Instantiate the child List Table class<\/li>\n<li>Call <code>prepare_items()<\/code> &#8211; which handles the data prep prior to rendering the table<\/li>\n<li>Call <code>display()<\/code> &#8211; which does the actual rendering of the table<\/li>\n<\/ul>\n<p>These two methods set the wheels in motion. So let&#8217;s look at where and how these methods are called in the plugin.<\/p>\n<h3 id=\"adding-page\">Adding the Plugin Menu Page<\/h3>\n<p>The plugin has a submenu page under Users, which is where the custom User List Table is displayed.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/user-menu-list-table-page.png\" alt=\"user-menu for the list table\" width=\"600\" height=\"131\" \/><figcaption class=\"wp-caption-text\">Menu Page for the List Table<\/figcaption><\/figure>\n<\/div>\n<p>To see how I added the user submenu page, take a look at the <code>define_admin_hooks()<\/code> method in <code>inc\/core\/class-init.php<\/code> and the <code>add_plugin_admin_menu()<\/code> method in <code>inc\/admin\/class-admin.php<\/code> of the plugin.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"73ac776404d79cef444e469e31dc1f90\" data-gist-file=\"class admin\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/73ac776404d79cef444e469e31dc1f90.js?file=class+admin\">Loading gist 73ac776404d79cef444e469e31dc1f90<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>Notice the callback <code>load_user_list_table()<\/code>for <code>add_users_page()<\/code> in the gist. This is where the methods <code>prepare_items()<\/code> and <code>display()<\/code> are invoked.<\/p>\n<p>However, I am instantiating the subclass in <code>load_user_list_table_screen_options()<\/code> instead, which is the callback for the page hook. It executes just before the plugin page is loaded, and creating the instance of my child class here will allow WordPress to automatically add the columns of the List Table in the screen options panel. The <code>screen options<\/code> to control the pagination in the List Table are added here as well.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"77bcff4dcec25fdabe6292ac2976f7ab\" data-gist-file=\"gistfile 1\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/77bcff4dcec25fdabe6292ac2976f7ab.js?file=gistfile+1\">Loading gist 77bcff4dcec25fdabe6292ac2976f7ab<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>Also note that <code>display()<\/code> needs to be wrapped in an HTML form if you want to take advantage of features like bulk actions. I prefer to not mix the HTML which is why I used the <code>include_once<\/code> directive to load <code>partials-wp-list-table-demo-display.php<\/code> where the method is finally invoked.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"f9942e8a3b3f196fd9b28237cfdb8d36\" data-gist-file=\"partial wp list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/f9942e8a3b3f196fd9b28237cfdb8d36.js?file=partial+wp+list+table\">Loading gist f9942e8a3b3f196fd9b28237cfdb8d36<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<h3 id=\"verifying-setup\">Verifying the Barebone List Table Setup<\/h3>\n<p>When I opened the plugin page under the Users menu, WordPress noticed that a List Table is being requested, and as there was no data supplied to it, it rendered one with a message &#8220;No items found&#8221;.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/barebone-list-table-display.png\" alt=\"barebone-list-table-implementation\" width=\"600\" height=\"567\" \/><figcaption class=\"wp-caption-text\">The barebone User List Table<\/figcaption><\/figure>\n<\/div>\n<p>Notice how WordPress populated the HTML <code>form<\/code> elements, and the list table in the inspect view. It also automatically added the <code>class<\/code> attributes to control the styling to conform to the WordPress user interface standards. With the table rendering properly, let&#8217;s look at the other methods to complete the User List Table.<\/p>\n<p>Note: At this stage, WordPress will have generated a lot of warnings in the debug log due to the incomplete implementation of the subclass. Use a plugin like <code><a href=\"https:\/\/wordpress.org\/plugins\/developer\/\" rel=\"noopener\" target=\"_blank\">developer<\/a><\/code> to keep a watch on the WordPress log.<\/p>\n<h2 id=\"overriding-methods\">Overriding Methods of the WP_List_Table Class<\/h2>\n<p>Continuing with my top-down approach, I went ahead and added the table columns. To do this, you need to override <code>get_columns()<\/code> in the subclass.<\/p>\n<h3 id=\"get_columns\">WP_List_Table::get_columns()<\/h3>\n<p>The method expects an array that contains <code>key-value<\/code> pairs, where key is the column <code>slug<\/code>, and value is the text of the column&#8217;s title in your List Table.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"003870d2c0b799286db858a4650d087e\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/003870d2c0b799286db858a4650d087e.js?file=class+user+list+table\">Loading gist 003870d2c0b799286db858a4650d087e<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>Note the column <code>cb<\/code>, which is a special column that renders the checkboxes to use with bulk actions. I also modified the &#8220;No items found&#8221; message by overriding the <code>no_items()<\/code> method. Here&#8217;s my table after adding the column details.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/wp-list-table-get-columns.png\" alt=\"wp-list-table-get-columns method\" width=\"599\" height=\"309\" \/><figcaption class=\"wp-caption-text\">Columns retrieved after overriding get_columns()<\/figcaption><\/figure>\n<\/div>\n<p>With the table shaping up well, I then added the necessary code to <code>prepare_items()<\/code> to populate the data from the database into the List Table.<\/p>\n<h3 id=\"prepare_items\">WP_List_Table::prepare_items()<\/h3>\n<p>You have to provide your own implementation of <code>prepare_items()<\/code> to handle the table data. The method should be explicitly invoked just before rendering the table. So, all data related operations such as fetching, sorting, pagination, filtering, etc. must be handled here before the table is displayed.<\/p>\n<p>To load the table data, I queried the WordPress database in <code>fetch_table_data()<\/code> and stored the result in an array before assigning it to the <code>items<\/code> variable of the parent class.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"ca1b61fd5907e35b31f49ab4d02fe75d\" data-gist-file=\"class user table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/ca1b61fd5907e35b31f49ab4d02fe75d.js?file=class+user+table\">Loading gist ca1b61fd5907e35b31f49ab4d02fe75d<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>However, this won&#8217;t be sufficient to display data in the table. To complete the display, you also need to provide column-specific implementations using the <code>column_*()<\/code> methods.<\/p>\n<h3 id=\"column_default\">WP_List_Table::column_default()<\/h3>\n<p>In the method <code>get_columns()<\/code> above, I provided the column slugs for each column. These slugs can be used to identify the individual column methods. While it&#8217;s a good practice to provide column-specific methods for each column, you can also simply use the <code>column_default()<\/code> method instead. The <code>WP_List_Table<\/code> class defaults to <code>column_default()<\/code> when it can&#8217;t find a column-specific method.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"0a26ab4fd98753da41bbba4bdac04dab\" data-gist-file=\"gistfile 1\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/0a26ab4fd98753da41bbba4bdac04dab.js?file=gistfile+1\">Loading gist 0a26ab4fd98753da41bbba4bdac04dab<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>This rendered the columns, and I was able to see data in my List Table.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/wp-list-table-display-column-data.png\" alt=\"wp-list-table-display-column-data\" width=\"600\" height=\"427\" \/><figcaption class=\"wp-caption-text\">List Table data after adding the column_default() method<\/figcaption><\/figure>\n<\/div>\n<h3 id=\"column_cb\">WP_List_Table::column_cb()<\/h3>\n<p>If you want to use bulk actions to process rows, you will also need to provide an implementation for <code>column_cb()<\/code>, which is a special case of the column-specific method.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"80548107e962799953a8028eff881213\" data-gist-file=\"class user table list\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/80548107e962799953a8028eff881213.js?file=class+user+table+list\">Loading gist 80548107e962799953a8028eff881213<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>This rendered checkboxes for all the rows in my List Table. The value of the checkbox field should be unique such as a user_login or an id so that you can identify a record distinctly.<\/p>\n<p>I then added support for sorting and pagination in the List Table.<\/p>\n<h3 id=\"get_sortable_columns\">Sorting with WP_List_Table::get_sortable_columns()<\/h3>\n<p>Overriding <code>get_sortable_columns()<\/code> will make the specified columns sortable in the List Table. However, the code to actually sort the data needs to written in <code>prepare_items()<\/code>. In my example, I performed the sorting in the SQL itself while fetching the data in <code>fetch_table_data()<\/code> as shown above.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"16c5bbee662ee0b5cfb7856d2e6f1caa\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/16c5bbee662ee0b5cfb7856d2e6f1caa.js?file=class+user+list+table\">Loading gist 16c5bbee662ee0b5cfb7856d2e6f1caa<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>When a sort is performed on a column, its reference is available via the supergloabls <code>$_GET['orderby']<\/code> and <code>$_GET['order']<\/code>.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/wp-list-table-sortable-columns.png\" alt=\"wp-list-table-sortable-columns\" width=\"600\" height=\"222\" \/><figcaption class=\"wp-caption-text\">Defining the sortable columns in the List Table<\/figcaption><\/figure>\n<\/div>\n<h3 id=\"set_pagination_args\">Handling Pagination with WP_List_Table::set_pagination_args()<\/h3>\n<p>The screen options that I added earlier already had the controls to limit the data in the table. To link these with the table, I used <code>get_items_per_page()<\/code> and <code>set_pagination_args()<\/code> to set the pagination arguments in the <code>prepare_items()<\/code>.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"8c26c13cbabcfdd6a0601621defb70da\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/8c26c13cbabcfdd6a0601621defb70da.js?file=class+user+list+table\">Loading gist 8c26c13cbabcfdd6a0601621defb70da<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>To render a subset of data for the individual table pages, I used <code>array_slice()<\/code> on my result array prior to rendering it.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/wp-list-table-pagination.png\" alt=\"wp-list-table-pagination\" width=\"600\" height=\"546\" \/><figcaption class=\"wp-caption-text\">Adding pagination the List Table<\/figcaption><\/figure>\n<\/div>\n<p>Next, I added the Search Box, Bulk Actions and Row Actions to my List Table.<\/p>\n<h3 id=\"search_box\">WP_List_Table::search_box()<\/h3>\n<p>To add a search box, you need to wrap it in a form along with a hidden field to ensure it submits to the plugin&#8217;s page. I added these in the <code>partials-wp-list-table-demo-display.php<\/code> just before the display(). The handling of the data for the search is done in <code>prepare_items()<\/code>.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"5858b8e5b38cc716e09939661fc875b9\" data-gist-file=\"partial wp list\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/5858b8e5b38cc716e09939661fc875b9.js?file=partial+wp+list\">Loading gist 5858b8e5b38cc716e09939661fc875b9<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/wp-list-table-search-data.png\" alt=\"wp-list-table-search-data\" width=\"600\" height=\"396\" \/><figcaption class=\"wp-caption-text\">Adding a search box to the List Table<\/figcaption><\/figure>\n<\/div>\n<p>When a search is performed, the search key will be available via the superglobal <code> $_REQUEST['s']<\/code>. In my example, I manipulated the result array using <code>array_filter<\/code> and the search key.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"87c1c0bd3f1cd0e266d84efc219764c4\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/87c1c0bd3f1cd0e266d84efc219764c4.js?file=class+user+list+table\">Loading gist 87c1c0bd3f1cd0e266d84efc219764c4<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<h3 id=\"get_bulk_actions\">WP_List_Table::get_bulk_actions()<\/h3>\n<p>Bulk actions are added by overriding <code>get_bulk_actions()<\/code>, and you need to specify the action in an associative array. The actual handling of the actions needs to be performed in <code>prepare_items()<\/code>.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"6046c2827585f1c827f85104a4b60145\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/6046c2827585f1c827f85104a4b60145.js?file=class+user+list+table\">Loading gist 6046c2827585f1c827f85104a4b60145<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>On submitting the bulk action, two variables <code>action<\/code> and <code>action2<\/code> will be set. The value will depend on which <code>bulk action<\/code> you applied, the one above or below the table.<\/p>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/wp-list-table-bulk-actions.png\" alt=\"wp-list-table-bulk-actions\" width=\"599\" height=\"185\" \/><figcaption class=\"wp-caption-text\">Bulk Actions and Row Actions in the List Table<\/figcaption><\/figure>\n<\/div>\n<p>In my plugin, the <code>handle_table_actions()<\/code> method call in <code>prepare_items()<\/code> takes care of processing the user actions.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"794107228f482d6a9fe7cb8fe206120d\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/794107228f482d6a9fe7cb8fe206120d.js?file=class+user+list+table\">Loading gist 794107228f482d6a9fe7cb8fe206120d<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>Once the nonce is verified, I loaded the <code>partials-wp-list-table-demo-bulk-download.php<\/code> under <code>inc\/admin\/views\/<\/code> to handle the bulk actions.<\/p>\n<p>Finally, I added the table row action links.<\/p>\n<h3 id=\"adding-actions\">Adding Row Action Links to the List Table<\/h3>\n<p>To add row action links, you need to provide a column-specific implementation for the table column that should display the action links. In my plugin, I added two action links &#8211; <code>Add Meta<\/code> and <code>View Meta<\/code> to the user_login column.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"55de0dc72ef1a5bef5ea073a6ef77b0f\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/55de0dc72ef1a5bef5ea073a6ef77b0f.js?file=class+user+list+table\">Loading gist 55de0dc72ef1a5bef5ea073a6ef77b0f<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>In my example, when a row action is executed, the user id, a custom nonce, and the action are all supplied as part of the URL parameters.<code>\/users.php?page=nds-wp-list-table-demo&amp;action=view_usermeta&amp;user_id=11&amp;_wpnonce=9c38dd6c2c<\/code><br \/>\nThese are used to handle the user action in the <code>prepare_items()<\/code>. I handled the row actions in the same method <code>handle_table_actions()<\/code> as that of bulk actions.<\/p>\n<p><span style=\"font-weight: 400;\"><div class=\"gist\" data-gist=\"e7da4b75742ef4b1617d3b7559e4e076\" data-gist-file=\"class user list table\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/e7da4b75742ef4b1617d3b7559e4e076.js?file=class+user+list+table\">Loading gist e7da4b75742ef4b1617d3b7559e4e076<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div><\/span><\/p>\n<p>You can identify the action being peformed with <code>current_action()<\/code>, and direct the user accordingly. Take a look at the <code>partials-wp-list-table-demo-view-usermeta.php<\/code> under <code>inc\/admin\/views<\/code> to see how I processed the request for the View Meta action.<\/p>\n<p>That&#8217;s it! Here&#8217;s the completed List Table.<br \/>\n<div  class=\"wpdui-pic-regular  \">\n<figure class=\"wp-caption alignnone\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"attachment-600x600 size-600x600\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2017\/10\/custom-user-list-table.png\" alt=\"custom-user-list-table\" width=\"583\" height=\"600\" \/><figcaption class=\"wp-caption-text\">The custom User List Table built with the WP_List_Table class<\/figcaption><\/figure>\n<\/div>\n<h2>Wrapping Up<\/h2>\n<p>I hope you found this article useful. Here are some more examples of the WP_List_Table class being used in popular plugins:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/woocommerce\/woocommerce\/search?utf8=%E2%9C%93&amp;q=class-wp-list-table&amp;type=\" rel=\"noopener\" target=\"_blank\">WooCommerce<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/moderntribe\/the-events-calendar\/blob\/d9d5ca3d3a221cbb6039c14ad4f139fb54e487b6\/src\/Tribe\/Aggregator\/Record\/List_Table.php\" rel=\"noopener\" target=\"_blank\">The Events Calendar<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/easydigitaldownloads\/easy-digital-downloads\/search?utf8=%E2%9C%93&amp;q=wp_list_table&amp;type=\" rel=\"noopener\" target=\"_blank\">Easy Digital Downloads<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>WordPress administration tables, or list tables, are used extensively in admin areas to list posts, pages, users, etc. If you\u2019ve ever wanted to add such list tables with your own data to your plugin, WordPress provides a nifty way to do so with the WP_List_Table class. In this article, I&#8217;ll show you how to use [&hellip;]<\/p>\n","protected":false},"author":573954,"featured_media":168631,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"blog_reading_time":"","wds_primary_category":0,"wds_primary_tutorials_categories":0,"footnotes":""},"categories":[557,263],"tags":[10788],"tutorials_categories":[],"class_list":["post-168226","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","category-tutorials","tag-wp-admin"],"_links":{"self":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/168226","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/users\/573954"}],"replies":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/comments?post=168226"}],"version-history":[{"count":184,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/168226\/revisions"}],"predecessor-version":[{"id":210028,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/168226\/revisions\/210028"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media\/168631"}],"wp:attachment":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media?parent=168226"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/categories?post=168226"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tags?post=168226"},{"taxonomy":"tutorials_categories","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tutorials_categories?post=168226"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}