Code Snippet: Helper Class To Add Custom Taxonomy To Post Permalinks

Say you have a custom taxonomy called “sport” and you wanted to inject “football” into the permalink of posts that have the “football” term assigned to it. How would you go about doing that? Well below is a helper class that will make it easy to accomplish exactly that.

As a reminder, here’s how you go about registering a custom taxonomy using the register_taxonomy() function:

add_action( 'init', 'register_sport_taxonomy' );

function register_sport_taxonomy() {
	register_taxonomy( 'sport', 'post', array(
		'labels' => array(
			'name' => 'Sports',
		),
		'hierarchical' => true, // Makes for a better selection box on write screen
	) );
}

And now, for my helper class:

(feel free to highlight and then copy/paste this code into your favorite text editor to make it easier to read)

/**
 * A helper class for registering and handling a custom rewrite tag for a custom taxonomy.
 *
 * @version 1.1.0
 */
class Add_Taxonomy_To_Post_Permalinks {

	/**
	 * Stores the taxonomy slug that this class will be handling. Don't edit this.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	public $taxonomy;

	/**
	 * Stores the rewrite tag complete with percentage signs. Don't edit this.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	public $rewrite_tag;

	/**
	 * Initializes the class by calling Add_Taxonomy_To_Post_Permalinks::register()
	 * as well as registering a filter that runs in get_permalink().
	 *
	 * @since 1.0.0
	 *
	 * @param string $taxonomy A taxonomy slug. Use the same one that you used with register_taxonomy().
	 * @return array $optional_args Optional configuration parameters. See Add_Taxonomy_To_Post_Permalinks::register().
	 */
	function __construct( $taxonomy, $optional_args = array() ) {
		if ( ! $this->register( $taxonomy, $optional_args ) )
			return;

		// Normal posts
		add_filter( 'post_link', array( &$this, 'filter_post_link' ), 10, 2 );

		// Custom post types
		add_filter( 'post_type_link', array( &$this, 'filter_post_link' ), 10, 2 );
	}

	/**
	 * Registers the rewrite tag using add_rewrite_tag().
	 *
	 * Can accept an array of optional parameters:
	 *
	 * * tagname: The rewrite tag to use (no percentage signs). Defaults to the taxonomy slug.
	 * * regex: What regex to use to validate the value of the tag. Defaults to anything but a forward slash.
	 *
	 * @since 1.0.0
	 *
	 * @param string $taxonomy A taxonomy slug. Use the same one that you used with register_taxonomy().
	 * @return array $optional_args Optional configuration parameters. See function description.
	 */
	public function register( $taxonomy, $optional_args = array() ) {
		if ( ! taxonomy_exists( $taxonomy ) )
			return false;

		$this->taxonomy = $taxonomy;

		$this->rewrite_tag = ( ! empty( $optional_args['tagname'] ) ) ? $optional_args['tagname'] : $this->taxonomy;
		$this->rewrite_tag = '%' . $this->rewrite_tag . '%';

		$rewrite_tag_regex = ( ! empty( $optional_args['regex'] ) ) ? $optional_args['regex'] : '([^/]+)';

		// See http://codex.wordpress.org/Rewrite_API/add_rewrite_tag
		add_rewrite_tag( $this->rewrite_tag, $rewrite_tag_regex );

		return true;
	}

	/**
	 * Filters a post permalink to replace the tag placeholder with the first
	 * used term from the taxonomy in question.
	 *
	 * @since 1.0.0
	 *
	 * @param string $permalink The existing permalink URL.
	 * @return object $post The post object that this permalink belongs to.
	 */
	public function filter_post_link( $permalink, $post ) {
		// Abort early if the placeholder rewrite tag isn't in the generated URL
		if ( false === strpos( $permalink, $this->rewrite_tag ) )
			return $permalink;

		// Get the custom taxonomy terms in use by this post
		$terms = get_the_terms( $post->ID, $this->taxonomy );

		// If no terms are assigned to this post, use the taxonomy slug instead (can't leave the placeholder there)
		if ( empty( $terms ) ) {
			$permalink = str_replace( $this->rewrite_tag, $this->taxonomy, $permalink );
		}

		// Replace the placeholder rewrite tag with the first term's slug
		else {
			$first_term = array_shift( $terms );
			$permalink = str_replace( $this->rewrite_tag, $first_term->slug, $permalink );
		}

		return $permalink;
	}
}

To use the class, simply initiate a new instance of it and pass the taxonomy slug to it while doing so:

add_action( 'init', 'register_sport_taxonomy' );

function register_sport_taxonomy() {
	register_taxonomy( 'sport', 'post', array(
		'labels' => array(
			'name' => 'Sports',
		),
		'hierarchical' => true, // Makes for a better selection box on write screen
	) );

	$sport_taxonomy_permalinks
		= new Add_Taxonomy_To_Post_Permalinks( 'sport' );
}

Then go to Settings → Permalinks and add %taxonomy% to your structure, such as /%sport%/%year%/%monthnum%/%day%/%postname%/. It will then be replaced by the first term (sorted by term ID) of your custom taxonomy.

More complicated solutions are of course possible, such as picking which of multiple terms you want injected into the URL or multiple terms in the URL, but my class should give you a good starting point to work off of. :)

13 thoughts on “Code Snippet: Helper Class To Add Custom Taxonomy To Post Permalinks

  1. Hello,

    I was wandering what to do if one had the following:
    · CPT: products
    · Taxonomy: product_types
    · Term: cellphones
    · Child-term: apple
    · custom post: iphone-4

    …and trying accomplish this;
    [ / products / cellphones / apple / iphone-4 ]

    …as in;
    [ / CPT / term / child-term(s) / postname ]

    The end result would be something like;
    /products » You view every custom post
    /products/cellphones » You view every post related to the term cellphones
    /products/cellphones/samsung/ » You view every post related to samsung

    Thank you,
    //DRSK

  2. Thanks for a very useful snippet. Is there a way to restrict taxonomy “injections” to specific post types?

    For example, I want posts in my post type ‘movies’ to have URLs containing taxonomy terms from the ‘genre’ taxonomy, and posts in my post type ‘software’ to have URLs using the ‘language’ taxonomy.

    • I would use a shared rewrite tag, such as %taxonomy% and then in your post_link callback function (filter_post_link() in my example), check what the $post->post_type is and vary what you inject into the URL based on that post type.

  3. Thanks Alex for this great & simple helper class.

    One little thing, you should add

    add_filter( 'post_type_link', array( &$this, 'filter_post_link' ), 10, 2 );

    after line 37 of your class to make it work with Custom Post Type as Well as normal Post.

  4. Hello,

    Amazing Code, Been looking for this for a long time and finally it WORKS!

    Two Questions:

    1 Is there a way to limit this code to only work on certain custom post types.
    2. Keeping 1 in mind, if there is no taxonomy assigned is there a way to not add the /tax-term/.

    for example, we have a custom post type called article and a taxonomy called topic.

    - if there is a topic i get the following permalink
    /fast-facts/awesome-days-straight-ahead/
    (fast-fact is the topic and than awesome-days-straight-ahead is the slug (dont want to change this)
    - if there is no topic i get the following permalink
    /topic/any-messianic-visions-in-your-neighborhood/

    so if there is no topic assigned to the post i want it to be just /slug instead of /topic/slug

    How can i accomplish this.

    Thanks

    -Yosef

    • My example code only works on the custom post types you enable it for. You can also set custom permalink structures per post type as a part of registering the post type.

      As for 2, you can probably replace this:

      $permalink = str_replace( $this->rewrite_tag, $this->taxonomy, $permalink );

      With this:

      $permalink = str_replace( $this->rewrite_tag . '/', '', $permalink );

      Untested though, it may not work.

      This most is more meant to give you a direction to go on rather than give you a final solution to all situations.

Leave a Reply