Allow co-editing of content by multiple users

WordPress comes with different roles, and it’s usually best not to give everyone the “Administrator” role. The next best role would be the “Editor” role, which enables a user to edit all contents, their own one and the content from other users. But in some cases, this role is also too permissive.

Allow a user to edit their content only

When you want to limit a user to edit only the posts and pages they have created, assign them to the “Author” role. With this role, they can see content of user users, but they cannot edit them. This is perfect, if you have a page where you want to invite many external users to edit “their pages”, maybe a page with a profile of their own company. They can either create this profile themselves, or you would create it and assign them as authors to that profile page.

But sooner or later you have the request from one of these users, that they want to have another person being able to edit this page as well. As WordPress can only have one author assigned to a page, this is not easily possible and as you probably don’t want to ask them to share the login password, we need a better solution for this.

The “Co-Authors Plus” plugin to the rescue

Through a tweet mentioning some plugins someone was regularly using, I found the plugin Co-Authors Plus, which has been around for many years, which tackles this need. It allows you to add more than one author to a post or page. The first author in the list will be shown at the single view of the post, but the plugin also offers functions to show the other authors as well (be adding those functions to a child-theme template).

Those different authors can even have different roles. But any user, who is either the primary/first author or an additional author, can edit the page, as long as their role would allow them to do so.

As WordPress usually stores the author of a post or page in a single column in the wp_posts table I was curious to see, how they solved it, and found out, that they use a custom post type to assign the authors to a post.

Conclusion

While the basic roles of WordPress are usually enough for various requirements, the issue of co-editing was always something, I was wishing for a core solution. But with the addition of this plugin (which still gets updates and even integrates nicely into the block editor and “Quick Edit”), this can also be established quite easily.

Overcome the “ERROR 1153” when importing large databases

Migrating a WordPress site is something I do a lot on a weekly basis. Many migrations happen between local environments or production and staging. The source and target systems don’t necessarily have the same settings. From time to time you have to migrate a site with a large database. In these cases, you might run into this error:

ERROR 1153 (08S01) at line 56: Got a packet bigger than 'max_allowed_packet' bytes

This can happen when a single INSERT statement on the import is too large. In this blog post, I want to give you tips on how to import the database.

Create a new backup with smaller imports

When you export your databases, you probably use either the mysqldump command, the wp db export or a database management tool such as Adminer or phpMyAdmin. In the default settings, they will create INSERT statements that would not only import a single line into a table, but multiple of them. This is usually a good strategy, as it speeds up the import. But if one of those statements become too long, you will run into the error.

To solve it, you can try to create an export of the database, that only has one single row in any INSERT statement. This will make the import run longer, but it’s more likely it succeeds.

To create an export like this, you can use the WP-CLI with some additional mysqldump arguments:

wp db export --extended-insert=FALSE --complete-insert=TRUE

This should create such an export file. Now you should hopefully be able to import that database dump.

Increase the “max_allowed_packet” value

If you are still unable to import the database dump file, you probably have to increase the value of “max_allowed_packet”. The default value can usually only be changed by the server administrator. But you can often increase the value temporarily. For this, you connect to the MySQL server, increase the value and import the file. I will use the wp db cli command from the WP-CLI. This will connect me to the correct database of my WordPress installation. Now I can run the commands:

SET GLOBAL max_allowed_packet=1*1024*1024*1024;
SOURCE db_dump_filename.sql;

The first command will set the variable globally to 1 GB. Unlike in the MySQL configuration file, you can not use a value of “1G” in this statement, but you have to use a number in bytes. But as you can also use a calculation while setting the value, in the above notation it’s easier to see, what the value in bytes will be.

Conclusion

Importing large databases can be an issue. But with these tips you can hopefully import it yourself. If not, ask a server administrator to help you out. They probably have tools to do the import for you. And if you yourself write data into the database, please make sure rows don’t get too large.

How to remove the “update lock” after a broken WordPress update

Updating WordPress and it’s plugins and themes is usually straightforward. You navigate to the “Dashboard | Updates” and update the components. If an error happens while updating WordPress Core or plugins/themes, WordPress can usually handle this and roll back, but sometimes things go wrong.

Failed updates break the site

If the update fails, your site might end up being broken. In this case, you will probably get the following message:

Briefly unavailable for scheduled maintenance. Check back in a minute.

When WordPress performs updates for core (or multiple plugins), it will enable a maintenance mode and inserts a lock into the database.

Repairing the site

The two locks will be automatically removed after 15 minutes. As it will not only break the backend, but also the frontend of your site, you probably don’t want to wait 15 minutes.

Deactivating the maintenance mode

The first thing you can do is searching for the file .maintenance file in the root folder of your WordPress installation. This file contains a single line of PHP code with the UNIX timestamp on when the installation process of the update started:

<?php $upgrading = 1648994440; ?>

If you delete this file, the message will disappear and WordPress will show the frontend and backend of your site again.

Resuming the previous update

Even after removing the .maintenance file, you can’t continue updating WordPress core. If you try to update it right away, you will probably see the following message:

Another update is currently in progress.

This is caused by the core_updater.lock option WordPress sets when starting the core update process:

$ wp option get core_updater.lock
1648994423

As you can see, it has a slightly earlier time (it’s set before the ZIP is being downloaded and extracted). When you are sure that the previous core update process really stopped, you can remove this option either using a database management tool or with the WP-CLI:

$ wp option delete core_updater.lock
Success: Deleted 'core_updater.lock' option.

Now you should be able to restart the update process, this time hopefully without an error causing your site to break again.

Conclusion

While WordPress updates usually don’t cause issues and run quite smoothly, an error can always happen. In these cases, you should know how to quickly get your site working again and not having it broken for 15 minutes.

Repair a broken Git Repository file system

Some weeks ago, I had quite an unusual issue with Git. In a repository I hadn’t used for quite a while, I simply wanted to see, if I had any changes, so I ran a git status on it with the following result:

$ git status
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
fatal: loose object 6eeab7d4770c705a0491cafbc95830af69d5c6a2 (stored in .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2) is corrupt

I’ve never experienced that before. Fortunately, Git offers some commands to check a Git repository, so I did a file system check:

$ git fsck --full
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
error: unable to mmap .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2: No such file or directory
error: 6eeab7d4770c705a0491cafbc95830af69d5c6a2: object corrupt or missing: .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2
Checking object directories: 100% (256/256), done.
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
error: object file .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2 is empty
fatal: loose object 6eeab7d4770c705a0491cafbc95830af69d5c6a2 (stored in .git/objects/6e/eab7d4770c705a0491cafbc95830af69d5c6a2) is corrupt

This gave me a bit more verbose information that one object was corrupt, but still no help in how to solve it, which Git usually gives you when using a command.

The Solution

With this new information, I was able to find a solution on Stack Overflow. I just had to delete the corrupt/empty file. In my case, it was really just this one file. In other repositories, there might be multiple files. To quickly delete them all, we can search the Git folder for all “empty” files and delete them:

$ find .git/objects/ -type f -empty | xargs rm

After this command, all corrupt files are missing from the repository. We can get them back by fetching the data from the remote:

$ git fetch -p
remote: Enumerating objects: 228, done.
remote: Counting objects: 100% (228/228), done.
remote: Compressing objects: 100% (91/91), done.
remote: Total 210 (delta 121), reused 188 (delta 99), pack-reused 0
Receiving objects: 100% (210/210), 90.23 KiB | 2.65 MiB/s, done.
Resolving deltas: 100% (121/121), completed with 11 local objects.

Finally, we make another file system check to see if all errors are gone:

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (221/221), done.
dangling blob c5446110414804bbba2a5316a3e996ff37666bb9
dangling blob 45dd1301284105bcfc7e183bc805b65bf1465f47
dangling blob 70376fcbe5060d0db11490249bed5b553c0d04cc

Conclusion

Usually, Git gives us quite useful error messages, when we do something wrong. In this case I had to research a bit but fortunately was not the first one to encounter this issue.

Our fix only worked without any losses, because we were able to fetch the deleted corrupt/empty objects from a remote. This is why I always advise people to always have their code in a remote repository as well, and commit and push often.

Volunteering at a WordCamp

Today’s blog post is a personal one and a call for people from the community. I want to talk about my experiences volunteering at a WordCamp.

My very first volunteering experience came quite late. I’ve attended my first WordCamp in 2010, organized the first event back in 2012 and spoke at many WordCamps since. Those types of contributions are also very valuable. But they are quite different from volunteering.

WordCamp Europe 2016 in Vienna

I don’t know exactly when I first thought about bringing WordCamp Europe to Germany, but back in 2015 I’ve joined a group of community members to talk with the current organizing team on how we could make it happen. When trying to organize an event this size, it’s usually best to have some experience in how it’s organized. So to me, it made a lot of sense to volunteer at the next event.

My first shift was as a “Foyer Guard” at the speaker’s green room and child care, which was quite interesting. I’ve only seen child care being offered at WordCamp London, but really felt that this was very important for some attendees travelling to Vienna with children.

My second shifts at the first day was at the registration welcoming new attendees. At this shift I was also not alone but joined by other volunteers. This made the shift much more fun, not only because I was able to work with other volunteers, but also because I was meeting so many people, some of which I have known for some year.

On the second day, my position was at the “Lunch Attendee Orientation”, so I basically told people where to get lunch – in case they haven’t had found it already at day one. My last shift was as a “Door Guard” for the largest stage. I made sure that attendees coming late would not make too much noise entering the room.

WordCamp London 2017, 2018 and 2019

My next volunteering change came in 2017 at WordCamp London. Only about a month before the event, the organizing team was asking for help, as they had some drop-offs amongst the volunteers. I already bought a ticket, but still volunteered as help was needed. I was also able to join an amazing “warm up” speakers, organizers and volunteers. It was a bar with “table tennis”! Really a lot of fun!

Just one year later, I volunteered again at WordCamp London. It’s one of my favourite WordCamps. At this event there was a kids workshop hosted by the WordPress co-founder Mike Little. I was amazed what some school kids were doing in these few hours the workshop lasted.

I volunteered a third time at WordCamp London in 2019. Here my first shift was in the storage room / cloakroom. I’ve helped sponsors store their boxes after setting up their booths and prepared some “swag bags” for attendees. After my shift, I’ve enjoyed the WordCamp and saw some session. Later, the WordCamp lead organizer found me to apologize. She said that she hasn’t realized, while planing the volunteers shifts, that she was putting the current Local Team Lead of WordCamp Europe into the cloakroom. But I assured her that there was no need to apologize. I was just a volunteer like any other and my help was needed in the cloakroom, so I worked my shift there.

Conclusion

Everyone who is helping out at a WordCamp as a volunteer is much needed and appreciated, no matter how small and insignificant your work might seem. Volunteering is also the first step of becoming a future organizer. There is no rule that you had to be a volunteer before joining an organizing team, but it helps a lot. Not only do you get a first idea of how organizing an event works, you will also know what it means to be a volunteer at an event, who might not even know anything about WordPress, and it’s community. So whenever there is a WordCamp near you, I would highly encourage you to look out for the Call for Volunteers to see if you can help. It will give you a very new perspective of the event.

And speaking of WordCamps who need more volunteers: WordCamp Europe is happening in June and you can still apply to volunteer until 31 March 2022. If this very blog post made you apply, please make sure to find me at “The Social” (this is what we call the “warm up” party at WordCamp Europe) and share some stories with me.

How to enqueue scripts and styles effectively

When using custom JavaScript and CSS files, you usually use the wp_enqueue_script() and wp_enqueue_style() functions. If not, please start doing so. Using them is really easy, but when it comes to effective usage, there are some little things to keep in mind.

Simple usage

The easiest way in using them is by only defining a “handle” and the URL to the file you want to enqueue:

wp_enqueue_script(
	'chart-js',
	'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js'
);

This example would load a JavaScript files hosted on an external server into your page. This can be all fine, but for many things you would rather want to have them locally, so you avoid issues when the CDN is down or with privacy.

Using a local file – bad example

To load a file in a plugin or theme, it would be enough to reference it with a relative path like this:

wp_enqueue_script(
	'chart-js',
	'/wp-content/plugins/plugin-name/assets/chart.min.js'
);

This would enqueue it in the source code in the following way:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=5.9'></script>

While this would work, there are some issues with this simple way.

1. Always use dynamic paths

In the example above, we use a relative path to the wp-content/plugins folder. This might seem OK for most of you, but as the wp-content folder can have a different name (some security plugins do that – which is usually not a great idea), you should always use helper functions to get the relative path to that folder. If you enqueue a file in a plugin or theme, there are different functions you can use. These are the ones, you would usually use:

2. Always provide a version to the file

As you can see in the example above, WordPress will add a version number. If you don’t define one yourself, it would append the current version number of WordPress, which today would be 5.9, to the end of the URL. This version number is meant to help you with caching. As your files will probably not (only) change when WordPress gets updated, you should use a different version string. This could be one of the following:

  • A static version number matching the version of the plugin
  • A static modification date of the plugin
  • A static modification date of the file that’s enqueued
  • A dynamic modification date of the file that’s enqueued

In the past, I have used the first or third option. But then you have to update all these string (for all files) manually and it’s easy to forget one, which will result in the browser loading old files from it’s cache. Nowadays I usually use the latest apporach. This also has the benefit that every time you save any file while still developing it, you don’t have to take care of the cache, your browser will always load the latest file. An example of this approach – combined with dynamic paths – could look like this:

wp_enqueue_script(
	'chart-js',
	plugins_url( 'assets/chart.min.js', __FILE__ ),
	array(),
	filemtime( plugin_dir_path( __FILE__ ) . 'assets/chart.min.js' )
);

This would then result in the file being included like this:

<script type='text/javascript' src='https://theme-tests.docker.test/wp-content/plugins/plugin-name/assets/chart.min.js?ver=1644792351' id='chart-js-js'></script>

As you can see, we now have a UNIX timestamp appended to the end of the URL. As this changes with every safe of the file, the browser will always load the most recent file version.

Conclusion

This blog post was covering a very basic concept, but I have seen so many plugins and themes out there still enqueuing files not in the best way. With these little tricks, you can avoid a lot of stress finding issues with your code, when in the end, it was just an old file being loaded from your browser’s cache.

Dynamic form specific hooks for GravityForms

I really like using GravityForms to create highly dynamic forms. The form builder offer a wide range of possibilities. But sometimes you need to use some code to hook into parts of the form processing. In this blog post I want to show you how to make this work even across multiple installations of a form.

Using a hook for all instances

Let’s take the gform_pre_submission as an example. If you want to hook into it, you would do as with any other hook in WordPress:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission', 'my_pre_submission' );

This code snippet would update every form field with ID 1 on every form. This is probably not what you want to do.

Using a hook for only one form

When changing a value, you usually do that for a specific form. This can be done by adding the form ID to the end of the hook:

function my_pre_submission( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_1', 'my_pre_submission' );

Now, the value only get updated when the form with ID 1 is submitted. That could be a good solution, until you export/import your forms.

Handling different form IDs

Let’s say you have created a form for one project, and now you want to use it in another project. You can imply use the export/import of forms to copy over all settings of a form (without the entries). But if your other project already has forms, then your imported form might have a different ID on this new project. The same could happen, if you copy the form from a development environment to a live site, or if you are not the only one creating new forms.

In this case, you would have to find out your new form ID and change the values for the hooks. But what if you have these hooks in version control, and you can’t change it to a new static value?

Using a constant for the form ID

In a project with this issue, I’ve decided to use constants to store the form IDs. All hooks now look something like this:

function my_pre_submission_contact( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_CONTACT_FORM_ID, 'my_pre_submission_contact' );

function my_pre_submission_form_event( $form ) {
    $_POST['input_1'] = 'updates value for field with ID 1';
}
add_action( 'gform_pre_submission_' . PREFIX_EVENT_FORM_ID, 'my_pre_submission_form_event' );

This is not the actual code, but I hope you get the idea. In using a constant per form (using the form name, not the ID), you can dynamically set the ID of the given form depending on the system the form is uses.

Set constant defaults in the plugin (or theme)

I usually store such code in a plugin. In the main file of this plugin, I would set the values for the constants on the system I’ve first used/created these forms:

if ( ! defined( 'PREFIX_CONTACT_FORM_ID' ) ) {
	define( 'PREFIX_CONTACT_FORM_ID', 1 );
}
if ( ! defined( 'PREFIX_EVENT_FORM_ID' ) ) {
	define( 'PREFIX_EVENT_FORM_ID', 2 );
}

By adding the ! defined() check, this would only set the constant, if it was not set earlier in another file. So on the second project, where the IDs are different, you would just define them in your wp-config.php for example:

define( 'PREFIX_CONTACT_FORM_ID', 4 );
define( 'PREFIX_EVENT_FORM_ID', 5 );

Conclusion

While GravityForm really offer a lot of hooks and using them is really flexible, it can be an issue when using a static form ID calling a hook. With a constant per dynamic ID value, you can use the same form and the same custom hooks in different projects.

The same would apply to all the other hooks from GravityForms where you can append an ID to only target a specific form, field, etc.

Sorting category pages alphabetically

In a Facebook group someone wanted to know how to order the posts on a category page alphabetically. In this blog post want to show, how you can easily sort posts on a category (or any other) archive page.

Sorting post on all category pages

Even though I don’t know why someone would like to order posts by name (archives of a different post type would probably make more sense), this is the code to order them:

function aa_sort_all_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category() ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_all_archives' );

Anytime you want to alter the query, you would preferably use a callback function to the pre_get_posts hook. In this callback you would first check, if the main query is run. Then you check for what ever condition makes sense for your use case. If this condition is not met, exit the function. If all check succeeded, alter the query. So in line 7 we check, if we are on a category archive. If so, we change the order of posts to “ascending” (line 11) and the field by which we want to sort (line 12) to the post_title field. That’s all.

Order only posts in a specific category

As for some post types it might be unlikely, that you want to sort posts of all categories, you can also pass the category slug (or it’s name, ID) to the is_category() function. In this example, I’m using a dedicated category with the slug “alphabetical” and sort only posts on this archive page:

function aa_sort_alphabetical_archives( $query ) {
	// Only sort main query.
	if ( ! $query->is_main_query() ) {
		return;
	}
	// Only sort category archives.
	if ( ! is_category( 'alphabetical' ) ) {
		return;
	}

	$query->set( 'order', 'ASC' );
	$query->set( 'orderby', 'post_title' );
}

add_action( 'pre_get_posts', 'aa_sort_alphabetical_archives' );

In the same way, you can have check for many other archive pages using different “Conditional Tags“. In the CODEX you will also find a page about “Alphabetizing Posts” but for category pages it tells you to use a secondary query. Please never do this! And also never show all posts! Just don’t do, what the page tells you 😉

Conclusion

Sorting posts (or other post types) on an archive page is best done by using the pre_get_posts hook. When you first use this hook, it might not work in the way you want it to work, but it’s worth learning how to use it correctly.

You can find the code of this blog post as a working plugin in a GIST where you can also download it as a ZIP and install it to your site.

2022: An eventful year

I usually don’t post “New Years” blog posts – I usually only have a post on the blog’s birthday – but as it’s still the last calendar week of 2021, and I’m unsure if I have to post something for #project26, I will tell you a bit about this new year from my perspective.

#project26 continues

This is either my last blog post of last year or the first one of this year 😉 Even though there are some things coming up this year, I will try to continue posting bi-weekly. I will also try to comment on other blogs, but I really struggle even reading lots of blog posts.

WordPress releases and Full Site Editing (FSE)

This year might be the first one with four major releases of WordPress. With the upcoming version 5.9 later this month, we will get a new default theme: TwentyTwentyTwo! So this year I’m finally planning to create my first FSE theme, either in a client project or by rewriting my currently used theme.

Retiring a plugin

With the release of 5.9 there will also be a new feature: the language switcher on the login screen. With this new feature, one of my first two plugins can finally be retired.

WordCamp Europe 2022 … in Porto!

The main “event” for me will be WordCamp Europe 2022, which will finally be held in Porto! Our organizing team is working hard to bring the community together. But as corona/covid will still be around, we will make sure, to also make it as safe for everyone as possible. After this edition, I will also retire from organizing WCEU – at least for some time.

Conclusion

The last two years have been tough for many of us. Finally, 2022 looks like the year we can enjoy some of the things we love! I can’t wait to see many of you either in Porto or on another WordCamp this year! With FSE being almost there, I also think that I will have some great topics to write about.

Using Block Patterns for new entries of a Custom Post Type

When developing projects, I often use Custom Post Types (CPT). In a new project, the content of the entries were build completely with core blocks. In the past, I typically used custom meta fields and a fixed page template. But as creating such a complex content of a CPT is quite a lot of work and error-prone, I wanted to use a template for new entries.

Creating a block pattern

The easiest way to create a pattern for a post type is by creating the content first using the block editor and then exporting the HTML markup using the “Copy all content” option:

Now you can use this markup to register a block pattern with the default content of new entries:

function cpt_register_block_pattern() {
	register_block_pattern(
		'my-cpt/my-cpt-pattern',
		array(
			'title'       => __( 'My CPT pattern', 'my-cpt' ),
			'description' => _x( 'The custom template for my_cpt', 'Block pattern description', 'my-cpt' ),
			'content'     => '
<!-- wp:paragraph {"placeholder":"Custom Post Type ..."} -->
<p></p>
<!-- /wp:paragraph -->

<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:image -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image --></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"placeholder":"Name ..."} -->
<h2></h2>
<!-- /wp:heading -->

<!-- wp:paragraph {"placeholder":"Phone ..."} -->
<p></p>
<!-- /wp:paragraph --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->',
		)
	);
}
add_action( 'init', 'cpt_register_block_pattern' );

Creating a template for new entries

When registering a new CPT, you can can also create default content by passing a template argument with a multidimensional array defining the template:

function cpt_register_post_type() {
	register_post_type(
		'my_cpt',
		array(
			'label'                 => __( 'Custom Post Type', 'my-cpt' ),
			'supports'              => array( 'title', 'editor' ),
			'public'                => true,
			'show_ui'               => true,
			// ... other settings
			'has_archive'           => true,
			'show_in_rest'          => true,
			'template'              => array(
				array(
					'core/paragraph',
					array(
						'placeholder' => __( 'Custom Post Type ...', 'my-cpt' ),
					),
				),
				array(
					'core/columns',
					array(),
					array(
						array(
							'core/column',
							array(),
							array(
								array(
									'core/image',
								),
							),
						),
						array(
							'core/column',
							array(),
							array(
								array(
									'core/heading',
									array(
										'level'       => 2,
										'placeholder' => __( 'Name ...', 'my-cpt' ),
									),
								),
								array(
									'core/paragraph',
									array(
										'placeholder' => __( 'Phone ...', 'my-cpt' ),
									),
								),
							),
						),
					),
				),
			),
		)
	);
}
add_action( 'init', 'cpt_register_post_type' );

As you can see in this little example, the array can get very complex, unreadable and hard to maintain. You would also have to convert the Block Pattern HTML to this PHP array format. So any change to the pattern would require you to do the work twice, trying not to make any mistake.

Combining Block Patterns and Custom Post Types templates

As the solution with the PHP array was not really something I wanted to use (the actual template was a lot more complex than the one shown in the example), I first tried the following:

// ...
'template'              => array(
	array(
		'my-cpt/my-cpt-pattern',
	),
),
// ...

I thought, that maybe the template can also use Block Patterns in the array and not only blocks. But this didn’t work. When searching for a solution, I found multiple issues on GitHub. There I found the core/pattern block, which makes the task very easy:

'template'              => array(
	array(
		'core/pattern',
		array(
			'slug' => 'my-cpt/my-cpt-pattern',
		),
	),
),

Unfortunately, the core/pattern block is not available in WordPress 5.8, but you can already use it with the Gutenberg plugin installed and activated. I really hope that it’s going to be available with WordPress 5.9 expected next month.

Conclusion

Both Block Patterns and templates for new Custom Post Type entries are really helpful. Combining them with the new core/pattern block can really increase usability a lot.