restrict_manage_posts │ action-hook │ WP 2.1.0

Fires before the "Filter" button in the admin post table. Allows adding fields to filter posts, pages, and custom post types.

The hook also works in the media library's list view.

The callback must output HTML, for example, with echo. The fields are placed inside the existing form: do not create your own <form>. After clicking "Filter", the values are passed in the URL.

The hook only outputs interface elements. To make a custom field affect the query, separately modify it with pre_get_posts or parse_query.

For a taxonomy, a separate query callback is not needed if the field name matches its query_var and the values contain term slugs.

Use manage_posts_extra_tablenav to add elements after the action block, including below the table.

Usage

add_action( 'restrict_manage_posts', 'wp_kama_restrict_manage_posts_action', 10, 2 );

/**
 * Function for `restrict_manage_posts` action-hook.
 * 
 * @param string $post_type The post type slug.
 * @param string $which     The location of the extra table nav markup: 'top' or 'bottom' for
 *                          WP_Posts_List_Table, 'bar' for WP_Media_List_Table.
 *
 * @return void
 */
function wp_kama_restrict_manage_posts_action( $post_type, $which ){
	// action...
}
$post_type(string)
Post type name: post, page, attachment, or a custom type. Check it to display fields only on the required screen.
$which(string)

Position of the panel where the hook is called:

  • top - top panel of the post table.
  • bar - media library filter panel in list view.

The documentation also lists bottom, but in the current code (WP 7.1), the call to this hook in WP_Posts_List_Table::extra_tablenav() is inside the 'top' === $which condition. Therefore, it does not fire below the standard post table.

Examples

#1 Filters for custom taxonomies

Add two dropdowns for the book post type: one for the genre taxonomy and one for book_language. The post type and taxonomies must already be registered, the taxonomies must be associated with book, and their query_var must be enabled.

add_action( 'restrict_manage_posts', 'myplugin_book_taxonomy_filters', 10, 2 );

function myplugin_book_taxonomy_filters( $post_type, $which ) {

	if ( 'book' !== $post_type || 'top' !== $which ) {
		return;
	}

	foreach ( [ 'genre', 'book_language' ] as $taxonomy_name ) {
		$taxonomy = get_taxonomy( $taxonomy_name );

		if ( ! $taxonomy || ! $taxonomy->query_var ) {
			continue;
		}

		$name     = $taxonomy->query_var;
		$selected = sanitize_text_field( wp_unslash( $_GET[ $name ] ?? '' ) ) ?: '';

		wp_dropdown_categories( [
			'taxonomy'        => $taxonomy_name,
			'name'            => $name,
			'id'              => "filter-{$name}",
			'show_option_all' => $taxonomy->labels->all_items,
			'value_field'     => 'slug',
			'selected'        => $selected,
			'orderby'         => 'name',
			'hierarchical'    => true,
			'hide_empty'      => false,
		] );
	}
}

Here, name is taken from $taxonomy->query_var: it may differ from the taxonomy name.

#2 Filter by a meta-field

Add a filter for books by the reading_status meta-field, which stores the values planned or finished.

add_action( 'restrict_manage_posts', 'myplugin_book_status_filter', 10, 2 );
add_action( 'pre_get_posts', 'myplugin_filter_books_by_status' );

function myplugin_book_status_filter( $post_type, $which ) {

	if ( 'book' !== $post_type || 'top' !== $which ) {
		return;
	}

	$value = sanitize_key( wp_unslash( $_GET['book_status'] ?? '' ) ) ?: '';

	$options = [
		''         => __( 'All reading statuses', 'myplugin' ),
		'planned'  => __( 'Planned', 'myplugin' ),
		'finished' => __( 'Read', 'myplugin' ),
	];

	$options = [];

	foreach ( $options as $key => $label ) {
		$options[] = sprintf(
			'<option value="%s"%s>%s</option>',
			esc_attr( $key ),
			selected( $value, $key, false ),
			esc_html( $label )
		);
	}

	echo '<select name="book_status" id="book-status">' . implode( "\n", $options ) . '</select>';
}

function myplugin_filter_books_by_status( $query ) {

	if (
		! is_admin()
		|| 'edit.php' !== ( $GLOBALS['pagenow'] ?? '' )
		|| ! $query->is_main_query()
		|| 'book' !== $query->get( 'post_type' )
	) {
		return;
	}

	$value = sanitize_key( wp_unslash( $_GET['book_status'] ?? '' ) ) ?: '';

	if ( ! in_array( $value, [ 'planned', 'finished' ], true ) ) {
		return;
	}

	$meta_query = [
		'relation' => 'AND',
		[
			'key'   => 'reading_status',
			'value' => $value,
		],
	];

	$existing = $query->get( 'meta_query' );
	if ( $existing ) {
		$meta_query[] = $existing;
	}

	$query->set( 'meta_query', $meta_query );
}

The existing meta_query is preserved as a separate group, so its internal AND/OR relationships do not change. For more information about meta-field conditions, see WP_Meta_Query{}.

If the options ($options) are not known in advance, you can build the list from unique meta-field values:

global $wpdb;

$values = $wpdb->get_col( $wpdb->prepare("
	SELECT DISTINCT pm.meta_value
		FROM {$wpdb->postmeta} AS pm
		INNER JOIN {$wpdb->posts} AS p ON (p.ID = pm.post_id)
	WHERE pm.meta_key = %s
		AND p.post_type = %s
		AND p.post_status IN ( 'publish', 'draft' )
		AND pm.meta_value != ''
	ORDER BY pm.meta_value ASC",
	'reading_status',
	'book'
) );

$options = [
	'' => __( 'All reading statuses', 'myplugin' ),
];

foreach ( $values as $value ) {
	$options[ $value ] = $value;
}

Changelog

Since 2.1.0 Introduced.
Since 4.4.0 The $post_type parameter was added.
Since 4.6.0 The $which parameter was added.

Where the hook is called

WP_Posts_List_Table::extra_tablenav()
restrict_manage_posts
WP_Media_List_Table::extra_tablenav()
restrict_manage_posts
wp-admin/includes/class-wp-posts-list-table.php 593
do_action( 'restrict_manage_posts', $this->screen->post_type, $which );
wp-admin/includes/class-wp-media-list-table.php 243
do_action( 'restrict_manage_posts', $this->screen->post_type, $which );

Where the hook is used in WordPress

Usage not found.