Opened 10 years ago
Last modified 3 days ago
#37376 new feature request
Make it possible for custom post type to have an archive but no single
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Awaiting Review | Priority: | normal |
| Severity: | normal | Version: | 4.6 |
| Component: | Posts, Post Types | Keywords: | needs-unit-tests has-patch |
| Focuses: | Cc: |
Description
Very commonly we'll have testimonials, faqs, and such custom post types, which warrant an archive, but don't make sense to have a single. It's possible to have a post type with no archive and a single, but not the other way around. Funnily enough, I find that we far more often could use what isn't available versus what is.
There are workarounds. One option is to make it a non-publicly-queryable post type and use a page template or something to display the results. Another option I've seen around is to hook into template_redirect on the single and redirect the user. Chances are there's no links on the front-end, so this really only occurs when users click on the link from edit-post admin-side.
Another thing to consider here is SEO plugins and the like which look to the definition of the CPT to build the sitemap. The redirect method ends up putting useless links in the sitemap. The page template method works fine, but obviously means the CPT has to be queried separately.
Would there be a downside to adding a sibling has_single option? If it's true by default then it's backwards compatible, and the single rewrites simply reflect the option.
Attachments (2)
Change History (8)
This ticket was mentioned in Slack in #core by peterwilsoncc. View the logs.
5 years ago
#6
@
3 days ago
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()returnfalsewp_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:
- "Can users view this post type on the front-end?" (REST API, Query Loop, archives)
- "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.
}
An alternate approach is a new helper function that works like is_post_type_viewable() maybe.
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>';
}
The issue here is that this feels like a breaking change, so maybe just the default behavior (returning an empty string) is more realistic, and less fragile.
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 editormeta-boxes.php- Hides Preview buttonclass-wp-posts-list-table.php- Hides View/Preview row actions
REST API:
class-wp-rest-posts-controller.php- No alternate link header, nopermalink_template/generated_slugfields
Sitemaps:
class-wp-sitemaps-posts.php- Excludes post types withhas_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 optionpost-excerpt- "Read more" linkpost-featured-image- Link optionpost-date- Link optionread-more- Entire block purpose is linkinglatest-posts- Post title linksbreadcrumbs- 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)
@swissspidy Added a first pass patch to better clarify what I'm thinking. Also added string support for has_single, similar to has_archive, to make it possible to explicitly set the root url for singles. With that it would be possible to have a unique post_name, archive url, and single url.
Please take a look at this and let me know if this is in the right direction. If it is, I'll start to put together tests.