Skip to content

Conversation

@kraftbj
Copy link

@kraftbj kraftbj commented Jan 30, 2026

This patch adds UI to the Categories admin page to clearly indicate which category is the default:

  1. Shows the default category first in the list
  2. Adds a "Default" label next to the default category name

This helps users quickly identify and understand the default category behavior, as discussed in the Trac ticket.

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

Before:
before-categories

After:
after-categories


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

github-actions bot commented Jan 30, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props kraftbj, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@kraftbj
Copy link
Author

kraftbj commented Jan 30, 2026

Test failures appear unrelated — CI tooling tripping.

Shows the default category first in the list and adds a "Default" label
next to its name in the categories admin screen.

See https://core.trac.wordpress.org/ticket/26268
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Terms list table in the admin to visually surface the default term, particularly for post categories, by pinning it to the top of the list and labeling it as “Default.”

Changes:

  • In display_rows_or_placeholder(), resolves the default category for the category taxonomy and renders it before the rest of the terms, while skipping it later in the non-hierarchical loop.
  • In _rows(), introduces logic to detect the default category ID and skip it during hierarchical recursion so it is not duplicated when already rendered first.
  • In column_name(), adds a “Default” label (with a taxonomy-default-label span) next to the term name when the row’s term matches the configured default term option.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 261 to 279
if ( $default_category ) {
$this->single_row( $default_category );
}

/*
* Some funky recursion to get the job done (paging & parents mainly) is contained within.
* Skip it for non-hierarchical taxonomies for performance sake.
*/
$this->_rows( $taxonomy, $this->items, $children, $offset, $number, $count );
} else {
if ( $default_category ) {
$this->single_row( $default_category );
}

foreach ( $this->items as $term ) {
if ( $default_category && $default_category->term_id === $term->term_id ) {
continue;
}
$this->single_row( $term );
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prepending the default category with $this->single_row( $default_category ); here without updating $count or otherwise integrating it into the paging logic means the "per page" limit is effectively exceeded: hierarchical lists will show number rows from _rows() plus this extra row, and in the non‑hierarchical branch the default term can appear on every page even when it is not part of the current slice of $this->items. This changes the semantics of pagination (screen options say N items per page but the table often renders N+1), and duplicates the default term across pages. If the goal is just to surface the default category at the top, consider only rendering it when $offset is 0 (first page) and/or adjusting the paging counters so the extra row is accounted for instead of being "outside" the pagination logic.

Copilot uses AI. Check for mistakes.
// Handle display of default category by showing it first in the list, capture default category id.
$default_category_id = false;
if ( 'category' === $taxonomy ) {
$default_category_id = (int) get_option( 'default_category' );
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_rows() re-fetches get_option( 'default_category' ) on every (possibly recursive) invocation and re-derives the default term ID, even though display_rows_or_placeholder() already resolves the default term object just above. To keep the logic DRY and avoid repeated option lookups during deep hierarchies, consider computing the default term ID once (e.g., in display_rows_or_placeholder() and passing it into _rows(), or caching it on the instance) and using that in both places where you skip the default term.

Suggested change
$default_category_id = (int) get_option( 'default_category' );
static $cached_default_category_id = null;
if ( null === $cached_default_category_id ) {
$cached_default_category_id = (int) get_option( 'default_category' );
}
$default_category_id = $cached_default_category_id;

Copilot uses AI. Check for mistakes.
Comment on lines +417 to +421
$default_term = get_option( 'default_' . $taxonomy );
$default_term_label = '';
if ( $tag->term_id == $default_term ) {
$default_term_label = ' &mdash; <span class="taxonomy-default-label">' . __( 'Default' ) . '</span>';
}
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using get_option( 'default_' . $taxonomy ) here will work for core taxonomies like category and link_category, but it will not pick up default terms registered via register_taxonomy( ... 'default_term' => ... ), which are stored under default_term_{$taxonomy} (see taxonomy.php:537-557 and post.php:5333-5337). If the intent is for the "Default" label to apply to all taxonomies that support default terms (not only categories/link categories), consider mirroring the pattern in map_meta_cap() by checking both default_{$taxonomy} and default_term_{$taxonomy} so the UI stays consistent with how default terms are modeled elsewhere.

Copilot uses AI. Check for mistakes.
@westonruter
Copy link
Member

One thing seems to be missing in the user interface and that is discovery for how to change the default category. Should there be a link to the Writing Settings screen, linking to the specific field on that screen?

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@kraftbj
Copy link
Author

kraftbj commented Feb 2, 2026

Should there be a link to the Writing Settings screen, linking to the specific field on that screen?

I'm open to that. The main intent was to simply explain versus the user needing to infer why that category is acting differently in the list.

- Only display pinned default category on first page to maintain correct per-page count
- Pass default_category_id as parameter to _rows() to avoid repeated get_option() calls during recursion
- Add "Change Default" link to row actions for the default category
- Add id attribute to Writing Settings default category row for fragment linking
@kraftbj
Copy link
Author

kraftbj commented Feb 2, 2026

Trying it as a row action:
Screenshot 2026-02-02 at 12 36 40

if ( 'category' === $taxonomy && $tag->term_id === (int) get_option( 'default_category' ) ) {
$actions['change-default'] = sprintf(
'<a href="%s">%s</a>',
admin_url( 'options-writing.php#default-category-row' ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is slightly better because it puts keyboard focus on the select element:

Suggested change
admin_url( 'options-writing.php#default-category-row' ),
admin_url( 'options-writing.php#default_category' ),

@westonruter
Copy link
Member

Trying it as a row action:

Good idea!

I had only been thinking about doing so from the Categories list table, at /edit-tags.php?taxonomy=category, like somewhere in this paragraph below the table:

image

Would adding a link there as well be redundant? Or helpful?

@kraftbj
Copy link
Author

kraftbj commented Feb 2, 2026

Oh, I like that. I don't think it would be redundant. "The default category can be renamed or changed, but not deleted" or something like that with "renamed" going to the edit category page for that term and "changed" going to the settings page seems lightweight and direct. Going to try that. I'll leave the row action too.

Just like us, I thought about the category in the table and you thought about the text at the bottom (which I didn't think about at all), this is an area that I'd rather just help people find what they need no matter where their focus is, especially if it can be done in a relatively "invisible" way if you're thinking about other things.

Update the help text below the Categories list table to include links
for renaming the default category and choosing a different default.
@kraftbj
Copy link
Author

kraftbj commented Feb 2, 2026

Used language similar to elsewhere in Core (src/wp-admin/user-edit.php:63 - __( 'Your username cannot be changed, but you can use other fields to enter your real name or a nickname, and change which name to display on your posts.' ) in the profile help center.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor point, but I think the $default_category_id variable can be eliminated in favor of just $default_category. And I think it would be slightly better to use null when it isn't WP_Term (but just a personal preference).

if ( 'category' === $taxonomy ) {
$default_category_id = (int) get_option( 'default_category' );
$default_category = get_term( $default_category_id, 'category' );
if ( ! $default_category || is_wp_error( $default_category ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would reduce the conditions and it would handle the case of someone erroneously filtering the return value of get_term() to be something that isn't falsey:

Suggested change
if ( ! $default_category || is_wp_error( $default_category ) ) {
if ( ! ( $default_category instanceof WP_Term ) ) {

}

foreach ( $this->items as $term ) {
if ( $default_category_id && $default_category_id === $term->term_id ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( $default_category_id && $default_category_id === $term->term_id ) {
if ( $default_category && $default_category->term_id === $term->term_id ) {

* Skip it for non-hierarchical taxonomies for performance sake.
*/
$this->_rows( $taxonomy, $this->items, $children, $offset, $number, $count );
$this->_rows( $taxonomy, $this->items, $children, $offset, $number, $count, 0, 0, $default_category_id );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->_rows( $taxonomy, $this->items, $children, $offset, $number, $count, 0, 0, $default_category_id );
$this->_rows( $taxonomy, $this->items, $children, $offset, $number, $count, 0, 0, $default_category->term_id );

Comment on lines +249 to +250
$default_category_id = (int) get_option( 'default_category' );
$default_category = get_term( $default_category_id, 'category' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$default_category_id = (int) get_option( 'default_category' );
$default_category = get_term( $default_category_id, 'category' );
$default_category = get_term( (int) get_option( 'default_category' ), 'category' );

Comment on lines +252 to +253
$default_category = false;
$default_category_id = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$default_category = false;
$default_category_id = 0;
$default_category = null;

Comment on lines +246 to +247
$default_category = false;
$default_category_id = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$default_category = false;
$default_category_id = 0;
$default_category = null;

/* translators: %s: Default category. */
__( 'Deleting a category does not delete the posts in that category. Instead, posts that were only assigned to the deleted category are set to the default category %s. The default category cannot be deleted.' ),
/* translators: 1: Default category name, 2: URL to edit the default category, 3: URL to Writing Settings. */
__( 'Deleting a category does not delete the posts in that category. Instead, posts that were only assigned to the deleted category are set to the default category %1$s. The default category cannot be deleted, but it can be <a href="%2$s">renamed</a> or you can choose a <a href="%3$s">different default category</a>.' ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this a lot.

'<strong>' . apply_filters( 'the_category', get_cat_name( get_option( 'default_category' ) ), '', '' ) . '</strong>'
'<strong>' . apply_filters( 'the_category', get_cat_name( $default_category_id ), '', '' ) . '</strong>',
esc_url( get_edit_term_link( $default_category_id, 'category' ) ),
esc_url( admin_url( 'options-writing.php#default-category-row' ) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per above.

Suggested change
esc_url( admin_url( 'options-writing.php#default-category-row' ) )
esc_url( admin_url( 'options-writing.php#default_category' ) )

</tr>
<?php endif; ?>
<tr>
<tr id="default-category-row">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't be needed if we link to the field directy.

Suggested change
<tr id="default-category-row">
<tr>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants