Why don’t my custom taxonomies show up in WordPress post edit pages?

If you use custom taxonomies in WordPress, they don’t automatically get added to the new Gutenberg block interface.

Fortunately this is an easy fix: you need to add this extra parameter to the arguments you set when you first define your taxonomy:

'show_in_rest' => true

so your taxonomy arguments might look like this:

$args = array(
    'hierarchical' => false,
    'public' => true,
    'show_ui' => true,
    'show_admin_column' => true,
    'show_in_nav_menus' => true,
    'show_in_rest' => true,
    'show_tagcloud' => true,
    'show_admin_column' => true,

And your post edit screen should now show your taxonomy:

Debugging WordPress Media imports

If you’re working with a complicated site then you may find that importing files and posts into WordPress is a bit of a black art – there are lots of places where things might go wrong. If the upload_url_path value for your site has been manually changed, then this might cause problems with WP export files.

Q: why would you change upload_url_path?

A: changing upload_url_path lets WordPress serve images etc. from a subdomain or different domain, which should improve site performance if you have an image-heavy site.

This is an example item entry from the WordPress eXtended RSS export file (this is a simplified entry, with most of the post data (comment status, stickiness, time, post ID etc. removed for clarity).

	<pubDate>Fri, 15 May 2009 20:20:12 +0000</pubDate>
	<guid isPermaLink="false">http://iso200.com/simple/wp-content/uploads/2009/05/dsc_63181.jpg</guid>
	<content:encoded><![CDATA[neon, sign, Rue de la Paix, Paris, France]]></content:encoded>
	<excerpt:encoded><![CDATA[Rue de la Paix, Paris, France]]></excerpt:encoded>

The problem is with this line:


The attachment url line doesn’t contain a protocol – either http or https – which is why my media import failed. A protocol isn’t needed when you change the upload_url_path value – but it is needed when WP is importing media.


1. Post-export: grep the file – add a transport protocol to the url – i.e. http: or https: Don’t forget to add the : after the protocol – the wp:attachment_url line should now look like this:


2. Pre-export: check your media library settings and make sure a protocol is included:

NB: you only see these options in your Media Settings page if you have changed the value of upload_url_path.

Removing auto-generated WordPress thumbnail images

If you have folders full of intermediate thumbnail sizes that you don’t want, you can easily select and delete all the thumbnails WordPress has generated using the right tools. This is particularly useful if you change thumbnail sizes and want to get rid of old images.

If you have a Mac, use ‘Find Any File‘ and use a regex pattern to search for images: -(\d{2,4})x(\d{2,4})\.(jpg|jpeg|png|gif)

The regex will select file names that end with a dash followed by thumbnail width by thumnbmail height and the usual image file name extensions. This is the standard WP thumbnail naming format.

‘Find Any File’ will give you a window of images and you can manage/delete as you want.

If you type an incorrect regex (search) string in, the app will helpfully flag the error:

If you have repeatedly imported a test file into a development server, this may leave you with a number of additional duplicate full-size images in your uploads folder – the regex above will not remove these though.

This string has found a lot of duplicates for me – but not all – but be aware that it may select images that you don’t want, depending on their naming format: (\d)-(\d)\.(jpg|jpeg|png|gif)


When you import a test file of WP posts (etc.) into a development server create a new Uploads folder each time you do a test import. This avoids the problem of having duplicate original files.

Working with excerpts in WordPress, or why isn’t this filter working?

When is an excerpt not an excerpt?

If you call get_the_excerpt() WordPress returns a string that looks like an excerpt – but it might not be. If your post doesn’t have a handcrafted excerpt, WordPress returns an automatically generated word-counted trimmed-down version of the full post content (see codex).

TL/DR: WordPress returns a string for get_the_excerpt() even when has_excerpt() is FALSE.

I’d used this bit of code to automatically add a ‘read more’ link at the end of every excerpt on a search page.

function ds_excerpt_read_more( $output ) {

	$read_more_link = ' (

	if ( has_excerpt() && ! is_attachment() & is_search() ) {

		$output .= " " . $read_more_link;

	return $output;
add_filter( 'get_the_excerpt', 'ds_excerpt_read_more'  );

Problem was, it wasn’t working everywhere – some posts didn’t have the modified excerpt.

has_excerpt() was the problem – it only returns TRUE if there is a manual excerpt.

So this works better:

function ds_excerpt_read_more( $output ) {

	$read_more_link = ' (

	if ( has_excerpt() && ! is_attachment() & is_search() ) {

		$output .= " " . $read_more_link;

	} elseif (is_search() &&  ! has_excerpt() )  {

		$excerpt_length = apply_filters('excerpt_length', 55);
		$output = wp_trim_words( $output, $excerpt_length, '' );
		$output .= " " . $read_more_link;

	return $output;
add_filter( 'get_the_excerpt', 'ds_excerpt_read_more'  );

but this is best:

function ds_excerpt_read_more( $output ) {

	if (  is_search() && ! is_attachment() ) {

		$read_more_link = ' (
		if ( ! has_excerpt() ) {

			$excerpt_length = apply_filters('excerpt_length', 55);
			$output = wp_trim_words( $output, $excerpt_length, '' );

			/* Finds the last full stop in the excerpt, removes everything after it. */
			if ( ( $pos = mb_strrpos( $output, '.' ) ) !== false ) {
				$output = substr( $output, 0, $pos + 2 );

		/* Appends a 'read more' link at the end of the excerpt - if it's a search */
		$output .= " " . $read_more_link;

	return $output;
add_filter( 'get_the_excerpt', 'ds_excerpt_read_more'  );

How to add a new Page Template to WordPress

You have to do two things to add a new page template to WordPress.

1. Add a file with the correct name

WordPress uses a template hierarchy – you need to give your template the right name. Either page-ID.php – where ID is the post ID of the page you are targeting, or page-SLUG.php – where SLUG is either the slug of the page you are targeting or an understandable name you can assign through the page interface to pages.

2. Add a ‘Template Name’ declaration at the start of your template file

For WordPress to pick your template up, you need to start each page template file with a Template Name string followed by a unique name for the template:

Template Name: Offer page template

How to remove your front page from WordPress search results

If you use a static front page for your WordPress site, depending on its contents and structure it may show up in your search results – you may (or may not) want this to happen.

If you don’t want to see your front page in your search results, add this code to your functions.php file:

add_action( 'pre_get_posts', 'edinburgh_wp_exclude_front_page_from_search' );
function edinburgh_wp_exclude_front_page_from_search( $query ) {
    if ( $query->is_main_query() && $query->is_search() ) {
        // Get an array with page ID of front page 
	$pageID = array(get_option('page_on_front'));
 	$query->set('post__not_in', $pageID);

If you use a ‘regular’ setup and have a home page and not a static front page, use this slightly different function to exclude the home page from search results.

add_action( 'pre_get_posts', 'edinburgh_wp_exclude_home_page_from_search' );
function edinburgh_wp_exclude_home_page_from_search( $query ) {
    if ( $query->is_main_query() && $query->is_search() ) {
        // Get an array with page ID of home page
	$pageID = array(get_option('page_for_posts'));
 	$query->set('post__not_in', $pageID);

The best way to customise the categories widget in WordPress

You may not like how WordPress’ Categories widget lists categories – particularly if you want to modify the list that’s displayed and hide some categories or have a custom order of categories (rather than alphabetical or by number of posts).

So what’s the easiest way to customise the display of the Categories widget??

You don’t.

Create a new *menu*, add the categories that you want in the order you want them, and add your new menu to the widget area.

So you can go from this:

to this:

You’ve created a custom post type, but WordPress says your posts can’t be found…

This strange thing happened to me earlier today – added a custom post type earlier this week, published an article this afternoon – but then I couldn’t view the published version – WordPress said it couldn’t be found (404 error).


Go to your dashboard, and then to settings/permalinks.

Scroll to the bottom and click “Save Changes”. This will force WordPress to update permalink settings and your posts in your custom post type should now work.

Removing page, post, tag, category, and taxonomy IDs from the body_class in WordPress

WordPress loves to add a wide range of CSS classes to the body of a page, depending on what type of page it is, what resources it uses, and so on. Some of this is helpful (like ‘archive’, ‘post’ and the page format classes – ‘single-format-gallery’, ‘single-format-image’ etc.), but some of the tags are just noise – like the tags that have the post/page/category/tag/term IDs. There are a couple of ways to remove tags that you don’t want by filtering the body_class function. You can remove all generated classes, or add new ones, but selectively removing classes is a bit more tricky, depending on what you want to remove.

This function will strip out the IDs WordPress adds to tags, categories, pages, posts and taxonomy pages.

add_filter('body_class','remove_ID_classes_from_body', 50 );
function remove_ID_classes_from_body( $classes ) {
	global $wp_query;
	$page_query = $wp_query->get_queried_object();

	if(is_tag() || is_tax() || is_category()) {	
		$classes = array_diff( $classes, [( is_tag() ? 'tag-' : ( is_tax() ? 'term-':'category-') ) . $page_query->term_id] );
	} elseif (is_single() || is_page() ) {
		$classes = array_diff( $classes, [( is_page() ? 'page-id-' :'postid-').$page_query->ID] );
	return array_unique( $classes );