Mendaftarkan templat blok melalui plugin di WordPress 6.7


WordPress 6.7 development is moving along, and one of the biggest features of the release has now landed in the Gutenberg plugin: block templates via plugins.



If you’ve ever built a plugin with front-end template output, you know how difficult it can be to play nicely with themes while also attempting to output content generated by your plugin. This is especially common when building custom post types, custom taxonomies, or even virtual pages (i.e., custom URLs). And it’s been an issue for well over a decade with no standard Core solution for addressing it. 



As of Gutenberg 19.1, which will eventually be merged into WordPress 6.7, you can begin registering block templates and defining default content for your plugins. Because it’s all built on top of the block system, both themes and users can customize the template.



In this tutorial, I will walk you through the basics of registering block templates from plugins, show you how to overwrite them via themes, and share some tips and tricks along the way.



It’s important to remember that we are still in the midst of the WordPress 6.7 development cycle. Anything could change with the methods shared in this tutorial at any time. I will update any information that changes throughout the release cycle.








Requirements and setup





To test this new feature, you must be running the following in your development environment:




  • Gutenberg 19.1+




  • WordPress 6.6+ (preferably testing against trunk)





Let’s also go ahead and set up a test plugin for this tutorial. Create a new devblog-plugin-templates folder in your WordPress /plugins directory. Then add a plugin.php file within that folder with this code:



<?php
/**
* Plugin Name: Developer Blog: Plugin Templates
* Description: Example code for registering plugin block templates with WordPress 6.7+.
* Version: 1.0.0
* Requires at least: 6.6
* Requires PHP: 7.4
* Requires Plugins: gutenberg
* Text Domain: devblog-plugin-templates
*/

add_action( 'init', 'devblog_register_plugin_templates' );

function devblog_register_plugin_templates()
// Add calls to wp_register_block_template() here.




Go ahead and activate this plugin.



You’ll notice that there’s a devblog_register_plugin_templates() function hooked to init in the code. You will use this throughout the tutorial to register your block templates.



The basics of template registration





WordPress 6.7 will include a wp_register_block_template() function, which lets you add a new template to the Site Editor:



wp_register_block_template( string $template_name, $args = array() )




The function accepts two parameters, which let you define how your template is registered:




  • $template_name: The name of the template in the form of plugin_uri//template_name (note that this requires a double //). plugin_uri should match the folder name of your plugin.




  • $args: An array of arguments for defining the template:

    • title: An internationalized title for the template.




    • description: An internationalized description of the template.




    • content: The default content (block markup) for the template when rendered in the editor or on the front end.




    • post_types: An array of post type slugs to make available to users as per-post custom templates.







As of Gutenberg 19.1, it is technically possible to use any string for the plugin_uri half of the $template_name parameter, and it will appear in the UI. But it won’t be tied to any particular plugin. There is an open ticket to restrict registration to active plugins.




Let’s try registering a basic template named Example. Add this code inside your devblog_register_plugin_templates() function in plugin.php:



wp_register_block_template( 'devblog-plugin-templates//example', [
'title' => __( 'Example', 'devblog-plugin-templates' ),
'description' => __( 'An example block template from a plugin.', 'devblog-plugin-templates' )
] );




Now go to Appearance > Editor > Templates in your WordPress admin. You should see a brand new menu item with your plugin’s name (Developer Blog: Plugin Templates in this case). On click, you will see the template you just registered:







You’ll notice that WordPress displays the Empty template text in this case because you haven’t (yet) defined any content for it.



wp_register_block_template() is a plugin-specific function and is not meant to be used from within a theme at this time. Technically, it’s possible to “register” a custom template by putting any template file in your theme’s /templates folder, but you can’t use the wp_register_block_template() to add a title and description. Instead, you’d need to filter default_template_types.




Registering a template with content





Let’s take this one step further and add some default content to the template. You do this by adding some block markup to the content argument. Change your code so that it looks like this:



wp_register_block_template( 'devblog-plugin-templates//example', [
'title' => __( 'Example', 'devblog-plugin-templates' ),
'description' => __( 'An example block template from a plugin.', 'devblog-plugin-templates' ),
'content' => '
<!-- wp:template-part "slug":"header","area":"header","tagName":"header" /-->
<!-- wp:group "tagName":"main" -->
<main class="wp-block-group">
<!-- wp:group "layout":"type":"constrained" -->
<div class="wp-block-group">
<!-- wp:paragraph -->
<p>This is a plugin-registered template.</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
</main>
<!-- /wp:group -->
<!-- wp:template-part "slug":"footer","area":"footer","tagName":"footer" /-->'
] );




You’ll notice that I kept this relatively simple with calls to the Header and Footer template parts, some wrapping Group blocks, and a Paragraph block. The basic markup was copied from the Twenty Twenty-Four theme.



In general, it would be good practice to copy any wrapping markup from the most current default theme for your plugin templates, which will make it the most compatible with many other themes. This is because many theme authors use the default themes as a starting point.

Because it’s all block markup, you can technically use anything, and it’ll work with any block theme without errors. But it will likely not fit perfectly into every theme’s design.




If you refresh the Templates screen, you should see your changes reflected in the template preview:







Of course, this template is completely customizable by the user, just like any other template.



Pro tip: cleaner template block markup





I’m not a fan of assigning block markup (HTML) to parameters in a PHP function. You lose things like syntax highlighting in your code editor, and it mostly just makes perfectly readable code look messy. It’d be better to add template markup to individual files.



To address this issue, I created a custom function for grabbing the content of a template file. We’ll use this function throughout the rest of the tutorial to keep code clean, so go ahead and add this to your plugin.php file:



function devblog_get_template_content( $template ) 
ob_start();
include __DIR__ . "/templates/$template";
return ob_get_clean();




You can use this function to get the content of a template file and assign it to the content argument instead. 



Before you do that, create a new /templates folder and add an example.php template file with your block markup:



<!-- wp:template-part "slug":"header","area":"header","tagName":"header" /-->

<!-- wp:group "tagName":"main" -->
<main class="wp-block-group">

<!-- wp:group "layout":"type":"constrained" -->
<div class="wp-block-group">

<!-- wp:paragraph -->
<p><?php esc_html_e( 'This is a plugin-registered template.', 'devblog-plugin-templates' ); ?></p>
<!-- /wp:paragraph -->

</div>
<!-- /wp:group -->

</main>
<!-- /wp:group -->

<!-- wp:template-part "slug":"footer","area":"footer","tagName":"footer" /-->




Now update your original wp_register_block_template() call to use this new function, grabbing the block markup from /templates/example.php:



wp_register_block_template( 'devblog-plugin-templates//example', [
'title' => __( 'Example', 'devblog-plugin-templates' ),
'description' => __( 'An example block template from a plugin.', 'devblog-plugin-templates' ),
'content' => devblog_get_template_content( 'example.php' )
] );




How to unregister a template





WordPress will also introduce a new wp_unregister_block_template() function for—you guessed it—unregistering block templates. Try dropping this line into your devblog_register_plugin_templates() function after your call for registering the Example template:



wp_unregister_block_template( 'devblog-plugin-templates//example' );




The function takes a single parameter of $template_name. This parameter must include both the plugin URI and template slug, separated by a double slash, as it was originally registered.



wp_unregister_block_template() can be called to unregister any plugin-registered template. But it cannot be used to unregister templates added by themes or users.



How plugin block templates work with themes and user customizations





As with any other block templates, users can edit them directly from the Appearance > Editor > Templates screen in the WordPress admin. This will give them more direct control than ever over their sites, and they can customize using an interface that they are already familiar with.



Because WordPress is introducing a standard method of registering templates, it means that plugins and themes will be able to integrate much easier than ever before. Even when a theme doesn’t specifically add template support for your plugin, you can still ensure that it works with any block theme.



If you’re a block theme author, you have the option of overriding the plugin’s default template content by creating your own version of the template. All you need to do is add the template to your theme’s /templates folder with any block markup you want.



For example, if you added a /templates/example.html template to your theme, it will appear like so under your theme’s template list in the UI:







Once a template lives within a theme, it will be under the Templates > Theme Name panel in the UI and no longer be listed under the original plugin panel.



You may have also noticed that the Example title now shows the template name of example and the description is missing when adding the template from a theme. There is a pull request that will fix this in Gutenberg 19.2.




Registering custom post, page, and CPT templates





Let’s make this tutorial more interesting and look at a real-world use case, something that you might encounter in your day-to-day life as a WordPress developer.



Suppose that you have a plugin that registers a custom post type that lets users share books. You need to control the default output of how these books are rendered on the front end, particularly for themes that don’t add support for the plugin.



First, register the book post type by adding this code to your plugin.php file:



add_action( 'init', 'devblog_register_book_type' );

function devblog_register_book_type()
register_post_type( 'book', [
'public' => true,
'show_in_rest' => true,
'capability_type' => 'post',
'has_archive' => 'books',
'menu_icon' => 'dashicons-book',
'supports' => [ 'editor', 'excerpt', 'title', 'thumbnail' ],
'labels' => [
'name' => __( 'Books', 'devblog-plugin-templates' ),
'singular_name' => __( 'Book', 'devblog-plugin-templates' ),
'add_new' => __( 'Add New Book', 'devblog-plugin-templates' )
]
] );




This is a bare-bones version of what your actual book post type might look like, but it provides everything needed to test out the new feature. Feel free to flesh it out by referencing the register_post_type() documentation.



The above code will add a new Books item to the WordPress admin menu. Now add a few quick demo posts for testing:







When working with custom post types, it’s important to understand and work with the Template Hierarchy. By using the standard template slugs, you don’t have to worry about doing any special front-end filtering. All that is required is registering the template itself for any templates that exist in the hierarchy.




Creating a single template





Let’s make a single template for the book post type you registered above. First, create a blank /templates/single-book.php file in your plugin’s folder. Then add this block markup to it:



<!-- wp:template-part "slug":"header","area":"header","tagName":"header" /-->

<!-- wp:group "tagName":"main" -->
<main class="wp-block-group">
<!-- wp:group "layout":"type":"constrained" -->
<div class="wp-block-group">
<!-- wp:spacer spacing -->
<div style="height:var(--wp--preset--spacing--50)" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<!-- wp:post-title "textAlign":"center","level":1 /-->

<!-- wp:spacer 30","style":"spacing":"margin":"top":"0","bottom":"0" -->
<div style="margin-top:0;margin-bottom:0;height:var(--wp--preset--spacing--30)" aria-hidden="true"
class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<!-- wp:post-featured-image "style":"spacing":"margin":"bottom":"var:preset /-->
</div>
<!-- /wp:group -->

<!-- wp:post-content "lock":"move":false,"remove":true,"layout":"type":"constrained" /-->
</main>
<!-- /wp:group -->

<!-- wp:template-part "slug":"footer","area":"footer","tagName":"footer" /-->




Again, this is using basic markup copied from the Twenty Twenty-Four theme. It displays the Post Title, Post Featured Image, and Post Content blocks. Feel free to tinker with it and add any blocks that you want.



Now register the template as you’ve done before inside the devblog_register_plugin_templates() function in your plugin.php file:



wp_register_block_template( 'devblog-plugin-templates//single-book', [
'title' => __( 'Single Book', 'devblog-plugin-templates' ),
'description' => __( 'Displays a single book.', 'devblog-plugin-templates' ),
'content' => devblog_get_template_content( 'single-book.php' )
] );




Take special note of the single-book slug used. This is the standard template name for a single post of the book post type. Using this slug will ensure that it works without any further effort on your part.



If you visit the Appearance > Editor > Templates screen in the admin, you will see the new Single Book template listed under the plugin panel:







Like any other template, this can also be overwritten by a theme or customized by users.



Creating a custom template





Aside from the normal single template for your post type, you can also create one or more custom templates that users can choose for a single post. Let’s create a new “Book: Canvas” template for the book post type, which gives the user an open canvas that only displays the Header, Post Content, and Footer.



Add this block markup to a new /templates/book-canvas.php file in your plugin:



<!-- wp:template-part "slug":"header","area":"header","tagName":"header" /-->

<!-- wp:group "tagName":"main","layout":"type":"default" -->
<main class="wp-block-group">

<!-- wp:post-content "lock":"move":false,"remove":true,"layout":"type":"constrained" /-->

</main>
<!-- /wp:group -->

<!-- wp:template-part "slug":"footer","area":"footer","tagName":"footer" /-->




Custom templates do not have a standard template naming convention, so I chose book-canvas in this case. The rule of thumb when naming is that you can use any name as long as it’s not a reserved name in the Template Hierarchy.



Registering custom templates also introduces the post_types argument for wp_register_block_template(). This argument lets you register a custom template for one or more post types, and users will be able to select this template when writing single posts.



Register the Book: Canvas template by adding this code inside your devblog_register_plugin_templates() function:



wp_register_block_template( 'devblog-plugin-templates//book-canvas', [
'title' => __( 'Book: Canvas', 'devblog-plugin-templates' ),
'description' => __( 'An open template for use with single posts. Includes the Header, Post Content, and Footer.', 'devblog-plugin-templates' ),
'post_types' => [ 'book' ],
'content' => devblog_get_template_content( 'book-canvas.php' )
] );




Like other templates, it will appear for your plugin on the Appearance > Editor > Templates screen. But it will also appear in a brand new location. 



First, head over to your Books admin screen and choose a book to edit. Under the Book panel in the sidebar, find the Template option and click it. From the popup, choose Swap template:







Now, you should see a modal overlay with an option for swapping to your Book: Canvas template. If you choose it, it will be used on the front end for this book:







Registering an archive template





Archive output is one thing most plugins that register post types will want to handle. Just like single templates, this is a standard template in the hierarchy.



Create a new file named /templates/archive-book.php in your plugin with the following block markup in it:



<!-- wp:template-part "slug":"header","area":"header","tagName":"header" /-->

<!-- wp:group "tagName":"main","align":"full","layout":"type":"constrained" -->
<main class="wp-block-group alignfull">

<!-- wp:query-title "type":"archive","align":"wide","style":"typography":"lineHeight":"1","spacing":"padding":20" /-->

<!-- wp:query "query":"perPage":10,"pages":0,"offset":"0","postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true,"align":"wide","layout":"type":"default" -->
<div class="wp-block-query alignwide">
<!-- wp:group "style":"spacing":"padding":spacing,"margin":"top":"0","bottom":"0","layout":"type":"default" -->
<div class="wp-block-group" style="margin-top:0;margin-bottom:0;padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">

<!-- wp:post-template "align":"full","style":"spacing":30","layout":"type":"grid","columnCount":3 -->

<!-- wp:post-featured-image "isLink":true,"aspectRatio":"3/4","style":"spacing":"margin":"bottom":"0","padding":20" /-->

<!-- wp:group "style":"spacing":"blockGap":"10px","margin":20","padding":"top":"0","layout":"type":"flex","orientation":"vertical","flexWrap":"nowrap" -->
<div class="wp-block-group" style="margin-top:var(--wp--preset--spacing--20);padding-top:0">
<!-- wp:post-title "isLink":true,"style":"layout":"flexSize":"min(2.5rem, 3vw)","selfStretch":"fixed","fontSize":"large" /-->
</div>
<!-- /wp:group -->

<!-- /wp:post-template -->

<!-- wp:spacer "height":"var:preset -->
<div style="margin-top:0;margin-bottom:0;height:var(--wp--preset--spacing--40)" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<!-- wp:query-pagination "paginationArrow":"arrow","layout":"type":"flex","justifyContent":"space-between" -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->

</div>
<!-- /wp:group -->
</div>
<!-- /wp:query -->

</main>
<!-- /wp:group -->

<!-- wp:template-part "slug":"footer","area":"footer","tagName":"footer" /-->




As earlier, this was copied and modified from the Twenty Twenty-Four theme. Feel free to customize it to your liking.



Now register the template by adding this code to your devblog_register_plugin_templates() function in your plugin:



wp_register_block_template( 'devblog-plugin-templates//archive-book', [
'title' => __( 'Book Archive', 'devblog-plugin-templates' ),
'description' => __( 'Displays an archive of all book posts.', 'devblog-plugin-templates' ),
'content' => devblog_get_template_content( 'archive-book.php' )
] );




Again, pay attention to the template slug, which is archive-book. This is the standard name recognized by WordPress in the Archive Template Hierarchy.



Like your other templates, Book Archive will now appear under your plugin’s panel on the Appearance > Editor > Templates screen. If you haven’t already tried, click on the template to edit it, which should look like this:







Upgrading the user experience





The real power of plugin-registered block templates is what you can do with them when coupled with features like Block Variations, Block Bindings, and more. As a plugin developer, you no longer have to hack your way about in an attempt to insert dynamic data into some unknown theme. Now, you’ll have control over exactly what’s output (well, at least what’s output as a default).



Once you get the hang of registering your own templates, I highly recommend reading the tutorial series that explains how to put all of these pieces together:







Building templates for virtual pages





Not all plugins register custom post types, taxonomies, or other standard object types that are represented in the WordPress template hierarchy. There are times when you’re building custom URLs using the Rewrite API.



While building custom rewrite rules is outside the scope of this tutorial, I did want to show a basic implementation of how to combine these types of virtual pages with the block template system.



For the following sections, let’s suppose you want to register a template that’s used when a site visitor lands on yoursite.tld/?all-categories=1, which lists all of a site’s categories. Of course, in a real project, you’d add a rewrite rule for a pretty URL, but let’s keep this relatively simple for the purposes of this demonstration.



Registering an entirely custom template





Because WordPress doesn’t have a standard template for this scenario, the name you use for the template doesn’t really matter (as long as it doesn’t conflict with standard template names). 



Create a new file named /templates/all-categories.php in your plugin folder with this block markup:



<!-- wp:template-part "slug":"header","area":"header","tagName":"header" /-->

<!-- wp:group "tagName":"main" -->
<main class="wp-block-group">

<!-- wp:group "layout":"type":"constrained" -->
<div class="wp-block-group">

<!-- wp:spacer spacing -->
<div style="height:var(--wp--preset--spacing--50)" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->

<!-- wp:heading "level":1 -->
<h1 class="wp-block-heading"><?php esc_html_e( 'Categories', 'devblog-plugin-templates' ); ?></h1>
<!-- /wp:heading -->

<!-- wp:categories "showHierarchy":true,"showPostCounts":true,"className":"" /-->

</div>
<!-- /wp:group -->

</main>
<!-- /wp:group -->

<!-- wp:template-part "slug":"footer","area":"footer","tagName":"footer" /-->




The code calls the standard Header and Footer template parts and outputs Heading and Categories blocks for the content area.



Now you need to make sure it’s registered with WordPress. Add this code to your devblog_register_plugin_templates() function:



wp_register_block_template( 'devblog-plugin-templates//all-categories', [
'title' => __( 'All Categories', 'devblog-plugin-templates' ),
'description' => __( 'Displays a list of all categories.', 'devblog-plugin-templates' ),
'content' => devblog_get_template_content( 'all-categories.php' )
] );




As with other templates, it will appear under your plugin’s templates list on the Appearance > Editor > Templates page in the admin:







And as usual, this template can be customized by the user or overwritten by the active theme. The difference is that it’s not actually used anywhere on the front end yet. You must tell WordPress when it should be applied.



Using the template on the front end





When a visitor lands on yoursite.tld/?all-categories=1, they will not see your plugin’s template by default. To ensure your template is used for that page, you need to add a filter to the template_include hook.



In particular, you’ll use two template functions to “locate” your plugin template:




  • locate_template(): This function attempts to find the highest-priority PHP template given a hierarchy of choices.




  • locate_block_template(): This function attempts to find the highest-priority block template from the list than the PHP template.





Add this code to your plugin.php file:



add_filter( 'template_include', 'devblog_template_include' );

function devblog_template_include( $template )




You’ll likely want to make some adjustments based on your specific scenario, but make sure you change all instances of all-categories for custom projects.



Now you should see your template on the front end when visiting yoursite.tld/?all-categories=1.



Overwriting the template from a theme





As a theme author, one of the first things I wanted to test this with was my personal theme. Despite using block markup copied from Twenty Twenty-Four, it worked reasonably well:







It was by no means perfect, but it did give me a solid starting point for cleaning it up to better match my theme.



So I added the following block markup to the /templates/all-categories.html file in my theme. You’re welcome to try it with your own themes, but it could need further adjustment.



<!-- wp:template-part "slug":"header","className":"site-header" /-->

<!-- wp:group
"tagName":"main",
"style":
"spacing":
"padding":80"


,
"layout":"type":"default"
-->
<main class="wp-block-group" style="padding-top:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--80)">

<!-- wp:group
"tagName":"article",
"layout":"type":"default"
-->
<article class="wp-block-group">

<!-- wp:group
"tagName":"header",
"style":
"spacing":base"

,
"layout":"type":"constrained"
-->
<header class="wp-block-group">
<!-- wp:heading "level":1 -->
<h1 class="wp-block-heading">Categories</h1>
<!-- /wp:heading -->
</header>
<!-- /wp:group -->

<!-- wp:group "layout":"type":"constrained" -->
<div class="wp-block-group">

<!-- wp:categories
"showPostCounts":true,
"className":"is-style-spread"
/-->

</div>
<!-- /wp:group -->

</article>
<!-- /wp:group -->

</main>
<!-- /wp:group -->

<!-- wp:template-part "slug":"footer","className":"site-footer" /-->




The theme-adjusted version of the template came out a little prettier:







I realize this is an overly simplistic example of what’s possible, but I wanted to illustrate that plugins with front-end output can play nicely with themes within this system. 



This feature is a huge step toward solving a decade-plus problem for WordPress extenders. And I’m eager to start using it in real-world projects.



Props to @aljullu, @ndiego, and @bph for feedback and review on this post.




source

Comments

Popular Posts