I’ve been searching for examples of this online and through the WP documentation on filters but I can’t find a suitable hook, so apologies for posting a question without a good example of what I’m trying to do!
When you add a link to text or to a button in the editor, you can search for the page/post you want to link to. What you can’t search for is a post type archive link.
I want to be able to type the name of a post type into the search box (pictured below), and include the post type archive link in the search results. In this example, I have a post type called members
that I’d like to link to.
I find the need to do this a lot, and I always end up just typing /post-type-link
into the box and leaving it at that, but I don’t think this is an elegant solution and is clunky for users.
I have tried to write some code, but I don’t believe I have the right hook:
function include_cpt_search( $query ) { if ( is_admin() && is_single() ) { $query->set( 'post_type', array( 'services' ) ); } return $query; } add_filter( 'pre_get_posts', 'include_cpt_search' );
Has anyone done this before? Know of a filter or hook I could work with? Anything really!
Advertisement
Answer
The Link format in the RichText toolbar uses the Search REST API endpoint at /wp/v2/search
, so although that endpoint doesn’t provide a specialized hook for filtering the response, you can use rest_post_dispatch
to add custom links to the search results returned via /wp/v2/search
.
So in the examples below, I’m checking if the route is /wp/v2/search
and if so, then we add the (custom) post type’s archive link. Also note that, you should provide an array with the items mentioned here (the LinkControl
component used by the Link format).
Basic Example
Includes only the (i.e. one) post type whereby its name matched exactly the search keyword.
add_filter( 'rest_post_dispatch', 'so_62472641', 10, 3 ); function so_62472641( $response, $server, $request ) { // Don't modify the data if the REST API route is not /wp/v2/search if ( 'post' !== $request->get_param( 'type' ) || '/wp/v2/search' !== $request->get_route() ) { return $response; } // Let's see if there's a post type that matched the search keyword. $search = $request->get_param( 'search' ); if ( ! $post_type = get_post_type_object( $search ) ) { return $response; } // Now add the post type archive URL, if any, to the response data. if ( $url = get_post_type_archive_link( $search ) ) { $data = (array) $response->get_data(); $data[] = [ 'id' => 'post_type-' . $search, 'type' => 'Post Type Archive', 'title' => $post_type->label, 'url' => $url, ]; $response->set_data( $data ); } return $response; }
Extended Example
Includes all post types whereby the name/label matched the search keyword.
add_filter( 'rest_post_dispatch', 'so_62472641', 10, 3 ); function so_62472641( $response, $server, $request ) { // Don't modify the data if the REST API route is not /wp/v2/search if ( 'post' !== $request->get_param( 'type' ) || '/wp/v2/search' !== $request->get_route() ) { return $response; } $search = $request->get_param( 'search' ); $post_types = get_post_types( [], 'objects' ); $extra_data = []; // Let's see if there's a post type that matched the search keyword. foreach ( $post_types as $obj ) { if ( $search === $obj->name || // look for the search keyword in the post type name/slug and labels (plural & singular) false !== stripos( "{$obj->name} {$obj->label} {$obj->labels->singular_name}", $search ) ) { if ( $url = get_post_type_archive_link( $obj->name ) ) { $extra_data[] = [ 'id' => 'post_type-' . $obj->name, 'type' => 'Post Type Archive', 'title' => $obj->label, 'url' => $url, ]; } } } // Now add the post type archive links, if any, to the response data. if ( ! empty( $extra_data ) ) { $response->set_data( array_merge( (array) $response->get_data(), $extra_data ) ); } return $response; }
Sample Output (for the second example above)
Note: The above is a screenshot of a real response, but I deliberately (via PHP) changed the domain name to example.com
(i.e. the actual domain name is different).
And the examples were both tried & tested working on WordPress 5.5.1 (latest release as of writing). Also, you can exclude the default post
post type, if you want to.
Additional Notes
It should be noted that the examples do not take into account the pagination, which means, if there were 10 post types that matched the search keyword, then they would always be included in the response (on page 1, 2, 3, etc.). So you might want to just use the first example because at least it always includes at most 1 post type only. However, with the second example, you can actually limit the
$extra_data
to, say, 5 items (per page – but it’s up to you on how to distribute the items per page).You can also use a custom search handler class, e.g. one that extends the default class (
WP_REST_Post_Search_Handler
) and use thewp_rest_search_handlers
hook to add your class to the list. Here’s a very basic example…In
your-class.php
:class My_WP_REST_Post_Search_Handler extends WP_REST_Post_Search_Handler { // ... you'll need to actually write the code on your own.. } return new My_WP_REST_Post_Search_Handler;
In the theme’s
functions.php
file or somewhere in your plugin:add_filter( 'wp_rest_search_handlers', 'my_wp_rest_search_handlers' ); function my_wp_rest_search_handlers( $search_handlers ) { $search_handlers[] = include_once '/path/to/the/your-class.php'; return $search_handlers; }