[wp-trac] [WordPress Trac] #37376: Make it possible for custom post type to have an archive but no single
WordPress Trac
noreply at wordpress.org
Thu May 7 13:55:00 UTC 2026
#37376: Make it possible for custom post type to have an archive but no single
----------------------------------------+------------------------------
Reporter: jason_the_adams | Owner: (none)
Type: feature request | Status: new
Priority: normal | Milestone: Awaiting Review
Component: Posts, Post Types | Version: 4.6
Severity: normal | Resolution:
Keywords: needs-unit-tests has-patch | Focuses:
----------------------------------------+------------------------------
Comment (by bacoords):
I've been working on refreshing this patch and wanted to document some
findings about the broader implications.
=== Core Approach ===
The patch adds a `has_single` property (boolean, default `true`) to
`register_post_type()`. When `false`:
* No rewrite rules are generated for single post views
* `get_permalink()` / `get_post_permalink()` return `false`
* `wp_get_shortlink()` returns empty string
=== The `is_post_type_viewable()` Problem ===
A key decision: '''`is_post_type_viewable()` intentionally does NOT check
`has_single`'''.
This function is used for two distinct purposes:
1. "Can users view this post type on the front-end?" (REST API, Query
Loop, archives)
2. "Does this post type have single post views?" (View links, sitemaps,
permalinks)
If we made `is_post_type_viewable()` return `false` for `has_single =>
false` post types, it would break:
* REST API public access
* Query Loop blocks
* Archive pages
* Any code checking general "publicness"
Instead, places that specifically care about single views must check
'''both''' `is_post_type_viewable()` AND `has_single`:
{{{
if ( is_post_type_viewable( $post_type ) && $post_type->has_single ) {
// Show view link, add to sitemap, etc.
}
}}}
=== The Permalink Assumption ===
WordPress core assumes `get_permalink()` always returns a URL string. This
patch changes that - it can now return `false`. Places generating links
must check:
{{{
$permalink = get_permalink( $post );
if ( $permalink ) {
echo '<a href="' . esc_url( $permalink ) . '">View</a>';
}
}}}
=== Files Changed in This Patch ===
'''Post type registration:'''
* `class-wp-post-type.php` - Adds property, skips single rewrite rules
'''Permalink functions:'''
* `link-template.php` - `get_post_permalink()`, `wp_get_shortlink()`,
`get_preview_post_link()` return false/empty
'''Admin UI:'''
* `edit-form-advanced.php` - Hides preview/view links in editor
* `meta-boxes.php` - Hides Preview button
* `class-wp-posts-list-table.php` - Hides View/Preview row actions
'''REST API:'''
* `class-wp-rest-posts-controller.php` - No alternate link header, no
`permalink_template`/`generated_slug` fields
'''Sitemaps:'''
* `class-wp-sitemaps-posts.php` - Excludes post types with `has_single =>
false`
=== Gutenberg PR Required ===
Many core blocks also assume permalinks exist for all post types. These
blocks need updates in the Gutenberg repo:
* `post-title` - Link option
* `post-excerpt` - "Read more" link
* `post-featured-image` - Link option
* `post-date` - Link option
* `read-more` - Entire block purpose is linking
* `latest-posts` - Post title links
* `breadcrumbs` - Ancestor links
The fix is straightforward - check if `get_the_permalink()` returns a
value before wrapping in `<a>`. I'll open a companion PR in Gutenberg.
=== Testing ===
Register a test post type:
{{{
register_post_type( 'no_single', array(
'label' => 'No Single',
'public' => true,
'has_archive' => true,
'has_single' => false,
'show_in_rest' => true,
) );
}}}
Verify:
* Archive page works
* REST API returns posts
* Query Loop displays posts
* No "View" links in admin
* Not in sitemaps
* Single URLs 404 (no rewrite rules)
--
Ticket URL: <https://core.trac.wordpress.org/ticket/37376#comment:6>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list