Make WordPress Core

Opened 15 months ago

Last modified 5 weeks ago

#62854 new feature request

Filter custom post types,terms in the admin area

Reported by: dhenriet's profile dhenriet Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Administration Keywords: has-patch
Focuses: Cc:

Description

Would it be possible to filter post types and taxonomy terms from the back office?

This would be a native use of the functionality added by this plugin:
https://wordpress.org/plugins/admin-taxonomy-filter/

Attachments (6)

1-screen-options-panel-default-state.png (374.8 KB) - added by sanket.parmar 5 weeks ago.
2-screen-options-panel-filters-checked.gif (287.2 KB) - added by sanket.parmar 5 weeks ago.
3-list-table-two-or-three-filters-enabled.png (343.2 KB) - added by sanket.parmar 5 weeks ago.
4-list-table-no-filters-enabled.png (357.8 KB) - added by sanket.parmar 5 weeks ago.
5-list-table-many-filters-enabled-wrapping.png (164.1 KB) - added by sanket.parmar 5 weeks ago.
6-url-query-string-filtering-in-action.png (345.8 KB) - added by sanket.parmar 5 weeks ago.

Download all attachments as: .zip

Change History (10)

#1 @audrasjb
15 months ago

  • Keywords needs-patch added

Hello and thanks for the ticket,

This would be technically and "philosophically" doable but we would also have to manage UI issues due to post types that use a lot of taxonomies. Maybe the best course of action would be to not display these filters by default and make them actionable via the screen option panel.

Workaround patches are welcome :)

This ticket was mentioned in PR #11225 on WordPress/wordpress-develop by @sanket.parmar.


5 weeks ago
#2

  • Keywords has-patch added; needs-patch removed

Adds taxonomy filter dropdowns to custom post type list tables in wp-admin, controlled via Screen Options. Filters are hidden by default; users opt in per-taxonomy. Enabled filters appear in a dedicated second row below the built-in date/category row. The preference is persisted per-user per-screen via user meta.

Testing instructions

  1. Register a CPT with at least two custom taxonomies (e.g. movie with actor and genre), add a few terms to each, and create some posts assigned to those terms.
  2. Navigate to Posts → Movies (or whatever the CPT slug is).
  3. Confirm the list table looks identical to a standard list table — no extra row, no new dropdowns.
  4. Open Screen Options. Confirm a new "Filters" fieldset is present listing your taxonomies.
  5. Check one taxonomy (e.g. "Actors"). Confirm a dropdown appears immediately in the second row below the built-in filters — no page reload required.
  6. Select a term from the dropdown and click Filter. Confirm the URL contains ?actor=keanu-reeves (slug, not ID) and only matching posts are shown.
  7. Check a second taxonomy. Confirm both dropdowns appear in the same second row with the Filter button at the end — not on a third line.
  8. Uncheck all taxonomy checkboxes. Confirm the second row disappears entirely.
  9. Reload the page. Confirm your checkbox preferences were remembered.
  10. With a term selected in the URL (e.g. ?actor=keanu-reeves), uncheck Actors in Screen Options. Reload that URL. Confirm the filter is silently dropped and all posts are shown (not zero results).
  11. Navigate to the front-end CPT archive. Confirm no change in front-end query behaviour.

Trac ticket: https://core.trac.wordpress.org/ticket/62854

## Use of AI Tools

#3 @sanket.parmar
5 weeks ago

Proposed patch for discussion


Background

Custom post types with multiple taxonomies have no native way to filter posts by those taxonomies in the admin list table. The only built-in dropdowns are for category and post_format on the default post type. Site builders and editors working with, say, a movie CPT with actor, director, genre taxonomies currently have no filter UI at all.

This patch adds first-class taxonomy filter dropdowns to WP_Posts_List_Table using the Screen Options panel as the control surface, directly addressing the core committer's guidance on the ticket:

"Maybe the best course of action would be to not display these filters by default and make them actionable via the screen option panel."


What the patch does

User-facing behaviour

  1. All taxonomy filters are hidden by default. The post list table looks identical to today for every post type.
  2. Users opt in per-taxonomy via Screen Options. A new "Filters" fieldset appears in the Screen Options panel, listing only the taxonomies that have at least one term. Users check the ones they want.
  3. Enabled filters appear immediately — no page reload — in a dedicated second row below the built-in date/category row.
  4. The preference is persisted per-user per-screen via user meta, so the choice survives page reloads and browser restarts.
  5. The Filter button is present in both rows so the user can apply date/category filters independently of taxonomy filters.
  6. Disabling all taxonomy filters hides the second row entirely.

Layout

[ Bulk actions ▼ ] [ Apply ]  [ All dates ▼ ] [ All Categories ▼ ] [ Filter ]
[ All Actors ▼ ]  [ All Genres ▼ ]  [ Filter ]   ← only when ≥1 taxonomy enabled

The taxonomy row has clear: left so it reliably starts on its own line, and display: flex so the Filter button never orphans onto a third line regardless of how many dropdowns are active.


Files changed

1. src/wp-admin/includes/class-wp-posts-list-table.php

__construct()

Two new hook registrations:

add_filter( 'screen_settings', [ $this, 'taxonomy_filter_screen_settings' ], 10, 2 );
add_action( 'pre_get_posts',   [ $this, 'handle_taxonomy_query_vars' ] );

Why here: The constructor is where all other list-table hooks are registered (manage_*_columns, restrict_manage_posts, etc.). Registering in the constructor ensures the hooks fire on every edit screen regardless of how the table is instantiated.


get_filterable_taxonomies( $post_type ) (new protected method)

Returns all show_ui taxonomies for the post type, excluding category and post_format.

Why exclude category and post_format: categories_dropdown() and formats_dropdown() already handle those. Adding them here would produce duplicate dropdowns.

Why show_ui check: Non-UI taxonomies (e.g. internal tagging systems) should not surface in the admin filter bar; they are invisible everywhere else in the UI by convention.


taxonomies_dropdown( $post_type ) (new protected method)

Renders <div class="taxonomy-filter-container [hidden]"> + <select> for each eligible taxonomy.

Key implementation decisions:

Decision Reasoning
wp_dropdown_categories() with value_field => 'slug' Term slugs work directly as WP_Query query vars and are install-independent (unlike IDs which vary per database).
All containers always rendered in the DOM Allows JavaScript to show/hide them instantly without a round-trip. Only the visible ones are shown on load.
namedata-name swap for hidden selects A <select> without a name attribute is never included in form serialisation, regardless of disabled state. This is a categorical guarantee — no reliance on JS running or disabled being preserved. disabled is also added as a secondary guard for accessibility tooling.
disable_taxonomy_dropdown filter Gives plugin/theme authors an escape hatch to suppress specific dropdowns programmatically. Follows the same pattern as the existing disable_categories_dropdown and disable_formats_dropdown filters.
hide_empty => true in get_terms() Only shows dropdowns for taxonomies that actually have terms. A dropdown with only the "All X" option is useless clutter.

taxonomy_filter_screen_settings( $settings, $screen ) (new public method)

Hooked on screen_settings. Appends a <fieldset class="metabox-prefs taxonomy-filter-prefs"> with one checkbox per taxonomy.

Key decisions:

Decision Reasoning
screen_settings filter This is the established hook for adding custom content to the Screen Options panel in WP core. render_screen_options() echoes it verbatim in the panel.
number => 1 in per-checkbox get_terms() Only fetch one term to confirm existence; the full list is fetched only in taxonomies_dropdown() where it is actually iterated. This avoids a potentially expensive query per taxonomy just for a presence check.
Term-existence check mirrors taxonomies_dropdown() A checkbox with no corresponding DOM container would silently do nothing when checked. Mirroring the same hide_empty check ensures every visible checkbox always has a container to show/hide.
Checked = visible, unchecked = hidden Consistent with the existing Columns section behaviour in the same Screen Options panel.

handle_taxonomy_query_vars( $query ) (new public method)

Hooked on pre_get_posts. Sanitises the query before WordPress executes it.

Case 1 — "All items" (?actor=0): wp_dropdown_categories() hard-codes value="0" for the "All items" option. WP_Query interprets ?actor=0 as "find the term with slug 0", finds nothing, and returns zero posts. This method unsets any taxonomy query var with value ‌'0' to restore the "show everything" behaviour.

Case 2 — Stale URL for a now-hidden taxonomy: A user might bookmark ?actor=keanu-reeves, then later uncheck Actors in Screen Options. Without this guard, navigating to that URL would still filter by the hidden taxonomy. This method unsets query vars for any taxonomy not in the user's visibility list.

Three guards ensure the callback only fires for the correct context:

  • is_admin() — prevents interference with front-end main queries (e.g. a CPT archive page).
  • is_main_query() — skips secondary admin queries (widgets, nav menu lookups, adjacent-post queries).
  • $query->get('post_type') !== $this->screen->post_type — skips unrelated edit screens or REST API admin-context queries for a different post type.

2. src/wp-admin/includes/screen.php

get_visible_taxonomy_filters( $screen ) (new function)

function get_visible_taxonomy_filters( $screen ) {
    $visible = get_user_option( 'manage' . $screen->id . 'taxonomyfilters' );
    if ( ! is_array( $visible ) ) { $visible = array(); }
    return apply_filters( 'visible_taxonomy_filters', $visible, $screen );
}

Pattern: Direct parallel of get_hidden_columns() — same file, same signature shape, same user-option naming convention (manage{$screen->id}*).

Why empty array default (all hidden): Implements "hidden by default" at the data layer. A new user or a new screen will always start with no filters visible.

Why visible_taxonomy_filters filter: Allows site builders to programmatically force certain filters visible (e.g. in a custom admin plugin) without touching user meta.


3. src/wp-admin/includes/ajax-actions.php

wp_ajax_taxonomy_filter_visibility() (new function)

Validates the screen-options nonce, sanitises the taxonomy slugs with sanitize_key(), and writes the preference to manage{$page}taxonomyfilters user meta.

Pattern: Exact mirror of wp_ajax_hidden_columns() — same nonce check, same page param, same update_user_meta call, placed directly after it in the file.


4. src/wp-admin/admin-ajax.php

Adds ‌'taxonomy-filter-visibility' to $core_actions_post.

Why needed: All core AJAX actions must be explicitly whitelisted here; the handler function alone is not sufficient.


5. src/js/_enqueues/admin/common.js

window.taxonomyFilters (new object)

Three methods, mirroring window.columns:

Method Purpose
init() Binds change on .taxonomy-filter-tog checkboxes. On enable: removes .hidden, swaps data-namename, removes disabled, shows the taxonomy row. On disable: adds .hidden, swaps namedata-name, adds disabled, hides the row if no filters remain. Calls saveState().
saveState() POSTs checked slugs to taxonomy-filter-visibility AJAX action. Updates the screen-reader live region via wp.a11y.speak().
visible() Returns comma-separated slugs of currently checked filters.

Why namedata-name in JS (mirroring PHP): When JS re-enables a filter, it must restore the name attribute so the select is included in the form submission. removeAttr('disabled') alone is not enough if the server never emitted a name attribute for hidden fields.

Why no page reload on toggle: Immediate visual feedback is critical for a Screen Options interaction. The existing columns object demonstrates this is the correct pattern in WP core.


6. src/wp-admin/css/list-tables.css

.tablenav .taxonomy-filters-row {
    clear: left;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 0;
}

.tablenav .taxonomy-filters-row.hidden {
    display: none;
}

.tablenav .taxonomy-filters-row .taxonomy-filter-container,
.tablenav .taxonomy-filters-row .taxonomy-filter-container.hidden {
    float: none;
}

.tablenav .actions + .taxonomy-filters-row {
    margin-top: 6px;
}
Rule Reasoning
clear: left Guarantees the taxonomy row always starts on its own line below row 1, regardless of viewport width. Without this, at very wide viewports both rows could share a line and the margin would be invisible.
display: flex; flex-wrap: wrap; align-items: center Children (taxonomy <div> wrappers + Filter button) become flex items that wrap inline as a single flow. The Filter button always sits immediately after the last dropdown — never orphaned on a third line, no matter how many taxonomies are enabled.
gap: 6px 0 Adds a small vertical gap only between wrapped lines within the row. No horizontal gap needed since .taxonomy-filter-container already inherits padding-right from .tablenav .actions.
float: none on .taxonomy-filter-container Cancels the inherited float: left from .alignleft so the containers participate correctly in flex layout, rather than being pulled out of flow.
.actions + .taxonomy-filters-row { margin-top: 6px } Adjacent-sibling selector: the gap only appears when a built-in filter row (div.actions) actually precedes the taxonomy row. Prevents orphaned top margin in the edge case where months/categories/formats all have nothing to show and row 1 is omitted.

What was intentionally left out

  • Standard post post type: category and post_format already have dedicated dropdowns and are excluded explicitly. post_tag is a free-text taxonomy rather than a hierarchical filter — a separate ticket could address it.
  • Bottom tablenav: extra_tablenav() is called for both top and bottom. Taxonomy filters only render on top, consistent with how months and category dropdowns behave.
  • Bulk-action row: Taxonomy filters are purely filter controls and never appear in the bulk-action area.

Screenshots for the ticket

The following screenshots would best illustrate the proposal for reviewers:

Screen Options panel — default state
The Screen Options panel open with the new "Filters" fieldset visible, all checkboxes unchecked. Shows the opt-in entry point and that nothing is visible out of the box.



Screen Options panel — filters checked
Same panel with two or three checkboxes checked. Capture it while the panel is still open so reviewers can see both the checked state and the rendered dropdowns appearing in the background simultaneously.



List table — two or three filters enabled
Full-page screenshot showing row 1 (dates / categories / Filter) and row 2 (e.g. "All Actors ▼ All Genres ▼ Filter") clearly separated with the gap. This is the primary "after" screenshot.



List table — no filters enabled
Same page with all taxonomy checkboxes unchecked. Row 2 is absent. Shows that the default experience is completely unchanged from core.



List table — many filters enabled (wrapping)
A CPT with 5–6 taxonomies all enabled. Demonstrates that flex-wrap keeps the Filter button on the same line as the last dropdown even when the row wraps internally.



URL / query string — filtering in action
Browser address bar showing e.g. ?post_type=movie&actor=keanu-reeves&genre=action after clicking Filter. Confirms that value_field=slug and handle_taxonomy_query_vars are working correctly (no ?actor=0 leaking through).


#4 @dhenriet
5 weeks ago

Amazing ! Thanks, guys ! Can't wait !

Last edited 5 weeks ago by dhenriet (previous) (diff)
Note: See TracTickets for help on using tickets.