Opened 4 months ago
Last modified 40 hours ago
#64991 new feature request
New param "allow_multiple_terms" in `register_taxonomy` for single term restriction
| Reported by: | bastho | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Taxonomy | Version: | |
| Severity: | normal | Keywords: | has-patch has-unit-tests needs-testing |
| Cc: | Focuses: | ui |
Description
The register_taxonomy function is based on the two main taxonomies: categories and tags.
Custom taxonomies can only have those two behaviors: hierarchical terms, like categories or non hierarchical terms like tags.
At this time, when a plugin adds a custom taxonomy, the generated UI is:
- checkboxes for category like taxonomies
- multiple select for tag like taxonomies
If a plugin needs to exclusive terms, the UI has to be hidden and a custom box added which causes UI inconsistency between plugins.
It would be very useful to natively allow single term behavior, just like post-format does.
In this case, the UI could be a simple select or radio buttons (preference for select).
I suggest a allow_multiple_terms parameter, with default value true
Setting it to false would make use the simple <select> field.
Caveats:
Need to manage: single edit page, quick-edit and bulk-edit.
Attachments (2)
Change History (4)
#2
@
40 hours ago
I tested the patch on a fresh WordPress development environment (Windows 10, Classic Editor, Test Single Taxonomy plugin).
What worked:
The single-term taxonomy UI is displayed as radio buttons.
Saving from both the post editor and Quick Edit works.
The selected term is displayed correctly in the Posts list.
Reopening the full editor correctly restores the saved term.
Issue found:
After changing the Book Status in Quick Edit (for example, from "Published" to "Draft") and clicking Update, the Posts list correctly displays Draft.
However, reopening Quick Edit for the same post does not preselect the saved term. All radio buttons are unselected, even though the full editor and the Posts list both show the correct saved value.
Result: The patch works partially, but Quick Edit does not restore the current single taxonomy selection.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Thanks for opening this, @bastho. Attaching a first patch (64991.diff) that covers the feature end-to-end, along with the reasoning behind a few decisions that came up while writing it. Happy to iterate on any of them.
What the patch changes
WP_Taxonomy — adds an $allow_multiple_terms property (default true) and accepts it in set_props(). When false and no custom meta_box_cb is supplied, the default meta box callback is switched to a new post_single_term_meta_box() and the sanitize callback to a new taxonomy_meta_box_sanitize_cb_single_term().
register_taxonomy() docblock — documents the new argument and a new @since 7.1.0 tag for the arg introduction.
post_single_term_meta_box() (new, in wp-admin/includes/meta-boxes.php) — renders radio buttons for hierarchical taxonomies and a <select> (via wp_dropdown_categories()) for non-hierarchical ones. A "None" option is offered when no default_term is configured.
taxonomy_meta_box_sanitize_cb_single_term() (new, in wp-admin/includes/post.php) — normalizes the submitted array to at most one term ID.
wp_set_object_terms() — enforces the cap at the shared assignment path, so REST, WP-CLI, and any direct caller honor the constraint — not just the admin UI. When $append is passed for a single-term taxonomy, the append is downgraded to a replace to avoid quietly stacking terms.
Quick Edit (WP_Posts_List_Table) — for hierarchical taxonomies, renders a radio list instead of wp_terms_checklist(); for non-hierarchical, renders a dropdown instead of the tags <textarea>. Bulk Edit already excludes per-taxonomy controls for non-category taxonomies, so the server-side cap in (5) is what protects bulk operations.
Design decisions worth flagging
Argument name: allow_multiple_terms. I kept the name you proposed. single_value reads cleanly as a property but pairs awkwardly with a default of true ('single_value' => true to mean "allow many" is a double negative). A meta_box_style enum was tempting for extensibility, but it conflates UI shape with cardinality — the server-side cap has to happen regardless of which UI is used, so a boolean flag that governs both defaults matches the semantics better.
Default is true. Every existing register_taxonomy() call is unaffected.
Enforcement lives in wp_set_object_terms(), not just the metabox sanitizer. This was the biggest design call. The alternative — only capping in the sanitize callback — would leave REST (POST /wp/v2/posts with a tax array of many IDs), WP-CLI, and any plugin calling wp_set_post_terms() free to bypass the constraint. Centralizing it at the assignment path matches how default_term and other taxonomy-level semantics are enforced, and keeps the contract consistent for API consumers.
"Last value wins" when multiple terms are submitted. This matches post-format semantics and means that if a form somehow submits two radio values (e.g. a custom client), the most recent one wins. Open to a stricter WP_Error instead if that's preferred.
Custom meta_box_cb precedence. If a caller supplies their own meta_box_cb, we don't override it even when allow_multiple_terms => false — the custom UI is assumed to know what it's doing. The server-side cap still applies, so a misbehaving custom UI can't save multiple terms.
Gutenberg / block editor. Out of scope for Core Trac — belongs in the gutenberg repo. The useEntityProp-backed PostTaxonomies component would need to read allow_multiple_terms (ideally exposed via the taxonomies REST schema) and render a <RadioControl> / <SelectControl> in place of the flat/hierarchical token field. Worth a companion ticket once the Core API shape is settled here. Also not yet exposed in the REST taxonomies endpoint — I didn't add it to WP_REST_Taxonomies_Controller::get_item_schema() in this patch because the API surface felt worth agreeing on first, but it's a natural follow-up.
Bulk Edit. The existing UI already suppresses per-taxonomy controls in bulk mode (! $bulk), and the cap in wp_set_object_terms() means bulk-updating many posts to a single term still works correctly. No per-row bulk UI change needed, though an explicit bulk-edit dropdown for single-term taxonomies could be a follow-up enhancement.
Open questions
Argument naming — fine as-is, or would committers prefer an alternative?
REST schema exposure — add allow_multiple_terms to the taxonomies endpoint schema in this patch or a follow-up?
default_term interaction — when allow_multiple_terms => false and a default_term is set, I'm suppressing the "None" option so the UI always reflects a valid selection. Happy to make that configurable if there's a use case for allowing explicit clearing.
Unit tests — haven't added them in this first pass; will follow up in a separate patch once the API shape is agreed, focused on wp_set_object_terms() cap behavior (including the $append downgrade), the new sanitize callback, and register_taxonomy() defaults wiring.