By default, the permalink for a custom post type (CPT) looks like /resources/post-title/. If you want the taxonomy term to appear in the URL — /resources/term-name/post-title/ — you need to replace the rewrite slug’s placeholder at permalink generation time and register a matching rewrite rule so WordPress can resolve those URLs.
Problem: A custom post type uses a hierarchical taxonomy, but the default permalink only includes the post slug — the taxonomy term is not part of the URL, making links less descriptive.
Solution: Use a %cat% placeholder in the CPT rewrite slug, add a matching rewrite rule for the archive URL, and hook into post_type_link to replace the placeholder with the actual term slug at permalink generation time.
Step 1. In your CPT registration arguments, use a placeholder in the rewrite slug and set has_archive to the archive URL segment you want:
<?php
$args = [
// … other args …
'rewrite' => [ 'slug' => 'resources/%cat%' ],
'has_archive' => 'resources', // archive page: /resources/
];
Step 2. Hook into post_type_link to replace the %cat% placeholder with the actual term slug at runtime. Also register a rewrite rule so that /resources/term-slug/post-slug/ resolves correctly:
<?php
add_filter( 'post_type_link', 'replace_resource_cpt_placeholder', 1, 2 );
function replace_resource_cpt_placeholder( $post_link, $post ) {
if ( ! is_object( $post ) || $post->post_type !== 'resource' ) {
return $post_link;
}
$terms = wp_get_object_terms( $post->ID, 'resource_category' );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
return str_replace( '%cat%', $terms[0]->slug, $post_link );
}
// Fallback: remove the placeholder so the URL is not broken
return str_replace( '%cat%/', '', $post_link );
}
add_action( 'init', 'register_resource_rewrite_rule' );
function register_resource_rewrite_rule() {
add_rewrite_rule(
'^resources/([^/]+)/([^/]+)/?$',
'index.php?post_type=resource&name=$matches[2]',
'top'
);
}
Step 3. After adding the code, flush the rewrite rules: go to Settings → Permalinks and click Save Changes.
Now /resources/ works as the CPT archive page and /resources/category-slug/post-slug/ resolves to the correct single post.
NOTE: This approach works for single terms. If a resource can belong to multiple categories and the term that appears in the URL is ambiguous, you will need additional logic to determine which term to use as the primary one (for example, the first assigned term or a designated primary term field).