Recently I have worked on a tour management website project where the client have purchased a tour theme and it required a few customizations. The theme has all the things related to tour management like tour package display, wishlist, user account management, online/offline payment management and many more. But the client required one more feature which is for destinations. He wanted to display destinations as per locations/states & destination category, which will only have details of various tour spots.
So here is the challenge.
I have to create a custom post type type called “destinations” (pretty simple) and two custom taxonomies locations & destination category associate with it. The harder part, to display destination categories (eg. Beaches, Hill Stations) taxonomy terms after a visitor click on a single location. And the hardest part, after clicking on a destination category it should display an archive page where it shows posts only associated with the location & destination category and finally to the destination CPT post.
Destination(Page from Menu) -> Locations (Custom Taxonomy) -> Destination Category (Custom Taxonomy) -> Destination Archive Page (Based on visits) -> Destination (The CPT post)
I will do this in 2 parts of coding, where in first part I’ll implement the custom URL for CPT. In second part I’ll create the pages within our flow to display contents as expected.
Part 1
URL for the destination post is like: http://www.mytourexample.com/{CPT}/{custom-taxonomy-1}/{custom-taxonomy-2}/{CPT-post-slug}/
So without talking more, lets get into the coding part.
At first I have registered a new CPT.
function register_destination_post_type() { $args = array( 'labels' => array( 'name' => __( 'Destinations', 'mt-destination' ), 'singular_name' => __( 'Destination', 'mt-destination' ), 'menu_name' => __( 'Destinations', 'mt-destination' ), 'name_admin_bar' => __( 'Destination', 'mt-destination' ), 'add_new' => __( 'Add New', 'mt-destination' ), 'add_new_item' => __( 'Add New Destination', 'mt-destination' ), 'new_item' => __( 'New Destination', 'mt-destination' ), 'edit_item' => __( 'Edit Destination', 'mt-destination' ), 'view_item' => __( 'View Destination', 'mt-destination' ), 'all_items' => __( 'All Destinations', 'mt-destination' ), 'search_items' => __( 'Search Destinations', 'mt-destination' ), 'parent_item_colon' => __( 'Parent Destinations:', 'mt-destination' ), 'not_found' => __( 'No Destinations found.', 'mt-destination' ), 'not_found_in_trash' => __( 'No Destinations found in Trash.', 'mt-destination' ) ), 'query_var' => 'mt_destinations', 'rewrite' => array( 'slug' => 'destinations/%destination_location%/%destination_category%', 'with_front' => false ), 'public' => true, // If you don't want it to make public, make it false 'publicly_queryable' => true, // you should be able to query it 'show_ui' => true, // you should be able to edit it in wp-admin 'has_archive' => 'destinations', //true, 'menu_position' => 51, 'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ), ); flush_rewrite_rules(); register_post_type('mt_destinations', $args); } add_action( 'init', 'register_destination_post_type' );
Here few things we have to remember while registering custom post type. Change the has_archive parameter to post type slug name and rewrite slug parameter to custom_post_type_slug/%taxonomy-1%/%taxonomy-2%.
By This we will have custom post type url as http://www.mytourexample.com/{CPT}/{custom-taxonomy-1}/{custom-taxonomy-2}/{CPT-post-slug}/, but before that we need to register our custom taxonomies and need to apply few filters.
function taxonomies() { $taxonomies = array(); $taxonomies['destination_category'] = array( 'hierarchical' => true, 'query_var' => 'destination-category', 'rewrite' => array( 'slug' => 'destination/category' ), 'labels' => array( 'name' => 'Destination Category', 'singular_name' => 'Destination Category', 'edit_item' => 'Edit Destination Category', 'update_item' => 'Update Destination Category', 'add_new_item' => 'Add Destination Category', 'new_item_name' => 'Add New Destination Category', 'all_items' => 'All Destination Category', 'search_items' => 'Search Destination Category', 'popular_items' => 'Popular Destination Category', 'separate_items_with_commas' => 'Separate Destination Categories with Commas', 'add_or_remove_items' => 'Add or Remove Destination Categories', 'choose_from_most_used' => 'Choose from most used categories', ), 'show_admin_column' => true ); $taxonomies['destination_location'] = array( 'hierarchical' => true, 'query_var' => 'location', 'rewrite' => array( 'slug' => 'destinations' ), 'labels' => array( 'name' => 'Location', 'singular_name' => 'Location', 'edit_item' => 'Edit Location', 'update_item' => 'Update Location', 'add_new_item' => 'Add Location', 'new_item_name' => 'Add New Location', 'all_items' => 'All Location', 'search_items' => 'Search Location', 'popular_items' => 'Popular Location', 'separate_items_with_commas' => 'Separate Location Categories with Commas', 'add_or_remove_items' => 'Add or Remove Location Categories', 'choose_from_most_used' => 'Choose from most used categories', ), 'show_admin_column' => true ); flush_rewrite_rules(); foreach( $taxonomies as $name => $args ) { register_taxonomy( $name, array( 'mt_destinations' ), $args ); } } add_action( 'init', 'taxonomies' );
Now after registering taxonomies we have to apply few filters to modify our CPT post URL as we want and have mentioned previously while registering CPT.
function filter_post_type_link($link, $post) { if ($post->post_type != 'mt_destinations') return $link; if ($cats = get_the_terms($post->ID, 'destination_category')) $link = str_replace('%destination_category%', array_pop($cats)->slug, $link); return $link; } add_filter('post_type_link', 'filter_post_type_link', 10, 2); function filter_post_type_link_location($link, $post) { if ($post->post_type != 'mt_destinations') return $link; if ($cats = get_the_terms($post->ID, 'destination_location')) $link = str_replace('%destination_location%', array_pop($cats)->slug, $link); return $link; } add_filter('post_type_link', 'filter_post_type_link_location', 10, 2);
This post_type_link filter changes the permalink for the post. For more explanation see this stackoverflow answer, which I followed to get this done.
After doing all this codes we can now have URL of Nathula Pass (destination post) which is in Sikkim (location taxonomy term) and a hill station (destination category taxonomy term) in category, like http://www.mytourexample.com/destinations/sikkim/hill-station/nathula-pass/
Part 2
After getting our post URL right, we will focus on our flow pages. Here is user flow for destinations.
Destination(Page from Menu) -> Locations (Custom Taxonomy) -> Destination Category (Custom Taxonomy) -> Destination Archive Page (Based on visits) -> Destination (The CPT post)
First 2 is the same, which will display locations if someone click on destination from main navigation menu of the site. It can be done easily so I’m skipping that here. Then comes destination category custom taxonomy page, which will show destination categories based on the location visitor clicked on.
<?php // get the currently queried taxonomy term, for use later in the template file $destination_location = get_queried_object(); // getting post ids that are assigned to current taxonomy term $destination_post_IDs = get_posts(array( 'post_type' => 'mt_destinations', 'posts_per_page' => -1, 'tax_query' => array( array( 'taxonomy' => 'destination_location', 'field' => 'slug', 'terms' => $destination_location->slug ) ), 'fields' => 'ids' )); // getting the terms of 'destination_category', which are assigned to these posts $destination_category = wp_get_object_terms($destination_post_IDs, 'destination_category'); echo '<ul>'; foreach( $destination_category as $category ){ echo '<li><a href="' . esc_url( site_url("destinations/location/".$destination_location->slug."/destination-category/".$category->slug) ) . '">'.$category->name.'</a></li>'; } echo '</ul>';
This is the code of my location taxonomy template page which is taxonomy-destination_location.php. By get_queried_object() function I am fetching the object of current taxonomy term. Then querying by the slug of this term I’m fetching the array of IDs of those posts, which are associated with this term and assigned it to $destination_post_IDs.
Now we get the list of posts which are associated with our location taxonomy term. Then we will look for the destination category taxonomy terms which are associated with these posts, by wp_get_object_terms() and store them in $destination_category variable. Then I run a loop to get each term name to display. You can follow this tutorial for more in-depth explanation of it.
Here now pay attention to the link of the anchor tag of the term name. It is the link of the page where I’ll list all my destination posts which are only associated with this 2 custom taxonomy term. It will be post archive page.
Normal archive page of post will display all the posts, but here I will use taxonomy query to specify my posts requirement through URL. And the link which I used on term name anchor is the pretty URL of my taxonomy query. Follow this tutorial on advanced taxonomy queries with pretty URLs, which I implemented here below.
I can get destination posts associated with bengal and beaches by this URL.
www.myexampletour.com/destinations/?location=bengal&destination-category=beach
But it will not look good as a URL within the flow of visitor looking for places to travel. So I make it look like this.
http://www.myexampletour.com/destinations/location/bengal/destination-category/beach/
To do this I have to tell WordPress about the URL structure, and how it should handle requests by this URL structure. For this we need to add rewrite rules as mentioned below.
function eg_add_rewrite_rules() { global $wp_rewrite; $new_rules = array( 'destinations/(destination-category|location)/(.+?)/(destination-category|location)/(.+?)/?$' => 'index.php?post_type=mt_destinations&' . $wp_rewrite->preg_index(1) . '=' . $wp_rewrite->preg_index(2) . '&' . $wp_rewrite->preg_index(3) . '=' . $wp_rewrite->preg_index(4), 'destinations/(destination-category|location)/(.+)/?$' => 'index.php?post_type=mt_destinations&' . $wp_rewrite->preg_index(1) . '=' . $wp_rewrite->preg_index(2) ); $wp_rewrite->rules = $new_rules + $wp_rewrite->rules; } add_action( 'generate_rewrite_rules', 'eg_add_rewrite_rules' );
After adding this rewrite rules now I can get only those destinations which are in bengal and beach in category by visiting the pretty URL mentioned above. Then we can design our archive-mt_destination.php file as per our requirement.
You can purchase the theme from here if you have tour management project. I liked all their modules, designs and page builder elements. It is very easy to create custom elements in their page builders.
Hi Pradip,
Thanks for your tutorial and it was of great help as I was struggling with it.
Just one question I have if you can answer this:
1- I have 2 custom taxonomies, say while creating custom post type I don’t select any of the one like I have services and procedures taxonomies so while creating a post I select only 1 of them it still shows the one i have not selected like this: %procedures% and then this page gives error.
Let me know if you can help.
Thanks,
Wajiha
Hey Wajiha,
I’m glad that this tutorial helped you by any means. 🙂
And for your question, this will not work if you don’t choose 2 taxonomies while adding a new post, because while registering post type we have specified
'slug' => 'destinations/%destination_location%/%destination_category%',
So, a post will have the slug with those 2 taxonomies term included.
Hey Pradip,
cool solution. I searched for a new project for this permalink-solution. Do you think this works with same two taxonomies and two (or multiple) custom-post-types too? I need custom-post-types like “sale” and “rent”. Both need the taxonomies (with many terms) like “city” and “type”.
best regards
Marcus
No, this solution will work for a custom post type with 2 taxonomies only. If you wish to query different post types based on city & type, then I think you should use query_var to get the city queried through URL and then write custom queries to fetch posts from different CPTs.
Currently, I can think of this solution only, there can be other solutions.
Thank you for your answer Pradip. An other idee was to create a copy of the taxonomy with another slug (posttype1-city, posttype1-type, posttype2-city, posttype2-type) and to rewrite this to posttype1/city/type and posttype2/city/type. What do you mean?
Sorry, didn’t get your question properly. But for rewrite URL & taxonomy queries you can have a look at the references which I provided in this article. Also, do some research on StackOverflow, I think it’ll help you with your issues.
Hey Pradip,
thank you. A solution with two posttypes works fine. But a have a problem with your filter (post type link) and I hope you can help me. I use to translate WPML an change the slug. The rewriting works, but the link brakes in the second language. I think i need something like this in die filters:
if (ICL_LANGUAGE_CODE == ‘de’) {
return ‘/de/’ . $link;
else {
return $link;
}
You know what I mean? Thanks Marcus
Hi Pradip,
Thanks for your tutorial, after I rewrite the cpt urls, all regular posts gives 404..
What am I missing?
Thanks
Hey Pradip,
Really it was a nice solution for me. coz i struggled a lot to find better solution for custom post permalink. But in my case, when i click on view post it shows page not found. Please do the needful.
This is an amazing tutorial! Thank you very much, it was very beneficial to me.
It’s a Membership website.
Listing ( Custom Post Type – 1 ) Team Member (Custom Post Type – 2)
When showing a single post for a listing Then showing below all Team members for this listing. But when I click a Team Member for a single post.
Then show WordPress Normal Permalink like this http://exampol.com/team-member ( custom post type -2 ) / single post link/
Normal WordPress permalink for listing. http://exampol.com/ listing ( custom post type -1 ) / single post link/
I want to show this type(Below) of permalink. Can it possible?
http://exampol.com/ listing ( custom post type -1 ) / single-post-link//team-member ( custom post type -2 ) / single-team-member-link/
we have follow all step but i am facing issue when i am clicking on hillstation link it shows 404 error but i want to see their post.
Hi,
Thank you so much for this tutorial. Because of this tutorial I have resolved few bugs of my website for which I was searching from last 10 days.
This really helped me a lot. Thank you so much.
hi.. Thankyou so much….it’s very helpful. You explained very well.
I want to ask how we can give thumbnail upload option in Custom Post -> in both Custom Taxonomy. Because I want to do it without plugin means that manually. In your code how I can give thumbnail upload option or featured image upload option in custom taxonomy. I tried to solve this problem through metabox but didn’t achieve that.
please sir if it is possible Provide solution As soon as possible. I am student.
Thankyou Sir..
Hey Pradip,
Really it was a nice solution for me. coz i struggled a lot to find better solution for custom post permalink. But in my case pagination is not working, when i click page 2 it’s shows page not found. Please do the needful.
Hi!
If use http://www.myexampletour.com/destinations/bengal (location) – works
If use http://www.myexampletour.com/destinations/beach (category) – doesn’t work. Error – 404
For example, the user will want to select a category, regardless of the location (to take into account all locations)