wordpress categories narrow down results

Yes and no, what you literally want is directly in conflict with a permalink pattern that already exists.

Namely, that if I have a term A, with a child B, B’s permalink would be:

/category/A/B

But if you wrote this expecting an intersection of C and B like this it would not work, it would interpret this as the archive for B, and potentially redirect:

category/C/B

For these reasons, what you literally asked for is not possible, and not desirable.

Showing Posts that have both terms via a URL without writing extra code

These methods work out of the box, assuming your template uses a standard main post loop, and does not use WP_Query/get_posts/query_posts or a page builder to display the category posts:

  • /category/A+B/
  • ?category_name=A+B

This works on all WordPress installs, and can even be appended to other WordPress archives to filter them.

Not only that, but because they’re URL parameters, they can be set up with form inputs and method="GET" to set up a filtering system super easily.

Creating New URLs

You could use rewrite rules, and do this with some kind of comparison type slug to avoid the clashes I mentioned earlier. e.g. /combine/categories/A/B.

Note that doing this will have the same SEO properties as using URL parameters, but with less flexibility, and the added cost of building it. The only benefit to this is that the URL will be prettier.

An Example That Almost Works When Copy Pasted

function custom_rewrite_rule() : void {
    add_rewrite_rule(
        '^combine/([^/]*)/([^/]*)/?','index.php?category_name=$matches[1]+$matches[2]',
        'top'
    );
}
add_action( 'init', 'custom_rewrite_rule', 10, 0 );

In theory, after flushing permalinks by visiting the permalinks page, this would allow URLs such as /combine/A/B/ to view posts that have both A and B.

Note that if you are using a category template that lists the main posts using a secondary query or page builder then this too will not work. The rewrite rules system feeds the main posts query, not secondary queries made with new WP_Query/etc