Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add registered templates endpoint
  • Loading branch information
ellatrix committed Oct 21, 2025
commit 7635f183f69720e2bc15c97851bc06cd3ba6f30b
2 changes: 1 addition & 1 deletion src/wp-includes/block-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function ( $template ) {
$query = array(
'slug__in' => $remaining_slugs,
);
$templates = array_merge( $templates, gutenberg_get_registered_block_templates( $query ) );
$templates = array_merge( $templates, get_registered_block_templates( $query ) );

if ( $specific_template ) {
$templates = array_merge( $templates, get_block_templates( array( 'slug__in' => array( $specific_template ) ) ) );
Expand Down
10 changes: 9 additions & 1 deletion src/wp-includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ function rest_api_default_filters() {
* @since 4.7.0
*/
function create_initial_rest_routes() {
global $wp_post_types;
Copy link
Member

Choose a reason for hiding this comment

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

Could you please add global documentation for global $wp_post_types ?

Copy link
Member Author

Choose a reason for hiding this comment

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

What would that documentation be? Looking through other instances of global $wp_post_types, I'm not seeing anything for those. 🤔

Copy link
Member

@shail-mehta shail-mehta Oct 21, 2025

Choose a reason for hiding this comment

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

Like this @global array $wp_post_types List of post types. ?

Reference PR: #72020

Copy link
Member Author

Choose a reason for hiding this comment

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

If it's ok I will leave this for a follow-up because we're very close to code freeze.


// Register the registered templates endpoint. For that we need to copy the
// wp_template post type so that it's available as an entity in core-data.
$wp_post_types['wp_registered_template'] = clone $wp_post_types['wp_template'];
$wp_post_types['wp_registered_template']->name = 'wp_registered_template';
$wp_post_types['wp_registered_template']->rest_base = 'wp_registered_template';
$wp_post_types['wp_registered_template']->rest_controller_class = 'WP_REST_Registered_Templates_Controller';
Comment on lines +268 to +273
Copy link
Member

Choose a reason for hiding this comment

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

@ellatrix We need to pick a shorter post type slug which is 20 characters or less, for example wp_registered_tmpl.

I discovered that this causes a very esoteric error in unit tests. It was difficult to debug, but it comes down to this: wp_registered_template is too long as a post type slug. The maximum length of posts.post_type in the database schema is 20 characters. When attempting to insert a post with this wp_registered_template post type, a database error ensues:

WordPress database error: Processing the value for the following field failed: post_type. The supplied value may be too long or contains invalid data.

This causes the unit tests in the WordPress/performance repo to start to fail. For example:

1) Test_OD_Storage_Post_Type::test_delete_all_posts
Failed asserting that false is identical to 'bar'.

/var/www/html/wp-content/plugins/performance/plugins/optimization-detective/tests/storage/test-class-od-url-metrics-post-type.php:464

The unit test doesn't fail when run in isolation. But it runs when running with all the other unit tests. This is because the Test_OD_REST_URL_Metrics_Store_Endpoint tests call rest_get_server() which now has the effect of running this code here to add wp_registered_template to $wp_post_types. The Test_OD_REST_URL_Metrics_Store_Endpoint tests aren't resetting the global $wp_post_types back to their original value (as this wasn't clear it was needed), so later when Test_OD_Storage_Post_Type runs it is looping over all get_post_types() to create some test posts of each post type to ensure their data remains intact when OD_URL_Metrics_Post_Type::delete_all_posts() is called. However, this ends up failing in the unit test when $post_type is wp_registered_template with the above database error:

$other_post_ids = array_merge(
	$other_post_ids,
	self::factory()->post->create_many( 10, compact( 'post_type' ) )
);

Copy link
Member

Choose a reason for hiding this comment

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

Ah, it seems this is being removed in #10425


foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
$controller = $post_type->get_rest_controller();

Expand Down Expand Up @@ -290,7 +299,6 @@ function create_initial_rest_routes() {
}

// Register the old templates endpoint.
global $wp_post_types;
$wp_post_types['wp_template']->rest_base = 'templates';
$controller = new WP_REST_Templates_Controller( 'wp_template' );
$wp_post_types['wp_template']->rest_base = 'wp_template';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

class WP_REST_Registered_Templates_Controller extends WP_REST_Templates_Controller {
public function register_routes() {
// Lists all templates.
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);

// Lists/updates a single template based on the given id.
register_rest_route(
$this->namespace,
// The route.
sprintf(
'/%s/(?P<id>%s%s)',
$this->rest_base,
/*
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
* Excludes invalid directory name characters: `/:<>*?"|`.
*/
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
// Matches the template name.
'[\/\w%-]+'
),
array(
'args' => array(
'id' => array(
'description' => __( 'The id of a template' ),
'type' => 'string',
'sanitize_callback' => array( $this, '_sanitize_template_id' ),
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}

public function get_item_schema() {
$schema = parent::get_item_schema();
$schema['properties']['is_custom'] = array(
'description' => __( 'Whether a template is a custom template.' ),
'type' => 'bool',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
);
$schema['properties']['plugin'] = array(
'type' => 'string',
'description' => __( 'Plugin that registered the template.' ),
'readonly' => true,
'context' => array( 'view', 'edit', 'embed' ),
);
return $schema;
}

public function get_items( $request ) {
$query = array();
if ( isset( $request['area'] ) ) {
$query['area'] = $request['area'];
}
if ( isset( $request['post_type'] ) ) {
$query['post_type'] = $request['post_type'];
}
$query_result = get_registered_block_templates( $query );
$templates = array();
foreach ( $query_result as $template ) {
$item = $this->prepare_item_for_response( $template, $request );
$item->data['type'] = 'wp_registered_template';
$templates[] = $this->prepare_response_for_collection( $item );
}

return rest_ensure_response( $templates );
}

public function get_item( $request ) {
$template = get_block_file_template( $request['id'], 'wp_template' );

if ( ! $template ) {
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
}

$item = $this->prepare_item_for_response( $template, $request );
// adjust the template type here instead
$item->data['type'] = 'wp_registered_template';
return rest_ensure_response( $item );
}
}
1 change: 1 addition & 0 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widget-types-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-widgets-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-templates-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-registered-templates-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-url-details-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-families-controller.php';
Expand Down