Add Categories to a Custom Post Type in WordPress

A custom post type in WordPress does not use the normal Categories and Tags unless you attach them. Even after you do, the category and tag archive pages still only list blog posts, so your custom posts never show up there.

This snippet does both: it attaches Categories and Tags to the post types you list, and adds those post types to the category and tag archives. It works with any custom post type, whether you registered it in code or with Custom Post Type UI.

The snippet

<?php
/**
 * Add Categories and Tags to custom post types, and list those posts
 * on the category and tag archives together with normal posts.
 */

// ===== Config =====
function dpc_cpt_categories_config() {
	$config = array(
		// Your custom post type slugs. Slugs that do not exist are ignored.
		'post_types'    => array( 'resource_libraries' ),
		// Taxonomies to attach to them.
		'taxonomies'    => array( 'category', 'post_tag' ),
		// Also include every post type made with Custom Post Type UI, when that plugin is active.
		'include_cptui' => true,
	);
	// ===== End of config =====

	return (array) apply_filters( 'dpc_cpt_categories_config', $config );
}

/** The configured post types that exist right now. */
function dpc_cpt_categories_post_types() {
	$config = dpc_cpt_categories_config();
	$types  = (array) ( $config['post_types'] ?? array() );

	if ( ! empty( $config['include_cptui'] ) && function_exists( 'cptui_get_post_type_slugs' ) ) {
		$types = array_merge( $types, (array) cptui_get_post_type_slugs() );
	}

	return array_values( array_unique( array_filter( $types, 'post_type_exists' ) ) );
}

// 1. Attach the taxonomies after the post types are registered.
add_action( 'init', 'dpc_cpt_categories_register', 99 );

function dpc_cpt_categories_register() {
	$config = dpc_cpt_categories_config();

	foreach ( dpc_cpt_categories_post_types() as $post_type ) {
		foreach ( (array) ( $config['taxonomies'] ?? array() ) as $taxonomy ) {
			register_taxonomy_for_object_type( $taxonomy, $post_type );
		}
	}
}

// 2. Show them on category and tag archives.
add_action( 'pre_get_posts', 'dpc_cpt_categories_archives' );

function dpc_cpt_categories_archives( $query ) {
	if ( is_admin() || ! $query->is_main_query() || $query->get( 'suppress_filters' ) ) {
		return;
	}
	if ( ! $query->is_category() && ! $query->is_tag() ) {
		return;
	}
	if ( '' !== $query->get( 'post_type' ) ) {
		return; // Something else already chose the post types.
	}

	$types = dpc_cpt_categories_post_types();
	if ( $types ) {
		$query->set( 'post_type', array_merge( array( 'post' ), $types ) );
	}
}

Add it with Scripts Organizer

In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor, edit the config and publish. Leave Action/Hook empty. Both parts run on hooks, init and pre_get_posts, after the post types are registered.

The same code also works unchanged in a small plugin or an mu-plugin.

How it works

  • dpc_cpt_categories_post_types() takes the slugs from the config and keeps only post types that exist, so a typo or a deleted post type cannot break the archive query.
  • With include_cptui it also adds every post type created with Custom Post Type UI, but only when that plugin is active. The earlier version called cptui_get_post_type_slugs() without checking and caused a fatal error on every category and tag archive when the plugin was off.
  • On init at priority 99, after post types are registered, register_taxonomy_for_object_type() adds Categories and Tags, so the boxes appear in the editor.
  • On pre_get_posts it changes only the main query of a category or tag archive on the front end, and only when nothing else has set a post type yet. It sets the post types to post plus your types. The earlier version had an operator precedence bug that applied its suppress_filters check to tags only.

Customise it

  • Your post types: replace resource_libraries in post_types with your slugs, for example array( 'book', 'event' ).
  • Categories only: set 'taxonomies' => array( 'category' ).
  • CPT UI users can also tick Categories and Tags under Taxonomies in the post type settings. The archive part of this snippet is still needed.
  • Pages too: see Add Tags and Categories to a Page.

Gotchas

  • The custom post type must be public for its posts to show on archives and have working links.
  • Category widgets and post counts include the custom posts once they are assigned, because the counts are per term.
  • If a theme template runs its own WP_Query for the archive, change that query too; this snippet only touches the main query.

In the Scripts Organizer library Import it with one click from Scripts Organizer.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You’ll also get DPlugins product updates. No spam, and you can unsubscribe anytime.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You'll also get DPlugins product updates. No spam, and you can unsubscribe anytime.