I've been working on this for a couple of days now. There have been many similar solutions here on this site, but none of them have worked out for me, or they nearly do, but then something else breaks.

For example, I tried this method: Using Custom Fields in Custom Post Type URL

But when I finally got it working, every page on the site which wasn't specifically an "accommodation" CPT returned 404 pages.

What I'm trying to do is to rewrite the URLs for accommodation pages from: /accommodation/%accommodation%/

to: /%country%/%region%/%accommodation%/

I also need to do the same for Region CPTs (/region/%region%/ => /%country%/%region%/) but haven't managed to get the accommodation rewrites fully working yet.

As part of the build, I have an ACF custom field for Post Object, which returns the post ID of selected country and region CPTs; this is already used in the build to pull in related content, etc. So for that reason, it makes sense to use these ACF fields to determine the values that need to go into the URL structure.

Here's what I'm working with at the moment:

function change_accommodation_post_type_link( $url, $post, $leavename ) {
    if ( 'accommodation' == $post->post_type ) {
        $country_object = get_field('accommodation_country');
        $region_object = get_field('accommodation_region');
        $country = get_post_field( 'post_name', get_post($country_object));
        $region = get_post_field( 'post_name', get_post($region_object));
        $url = site_url('/') . $country . '/' . $region . '/' . $post->post_name . '/';

    return $url;

function try_accommodation_before_page_in_parse_request( $wp ) {
    global $wpdb;

    if ( is_admin() ) return;

    if ( array_key_exists( 'name', $wp->query_vars ) ) {
        $post_name = $wp->query_vars['name'];

        $id = $wpdb->get_var( $wpdb->prepare(
            "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_name = %s ",
            'accommodation', $post_name
        ) );

        if ( $id ) {
            $wp->query_vars['accommodation'] = $post_name;
            $wp->query_vars['post_type'] = 'accommodation';

add_action( 'parse_request', 'try_accommodation_before_page_in_parse_request' );

add_filter( 'post_type_link', 'change_accommodation_post_type_link', 10, 3 );

Based on this: https://wordpress.stackexchange.com/a/305011

This works in as far as it changes the URL in the back-end, and it doesn't break any other pages, but it doesn't work. For example, the "Evergreen" accommodation URL is changed to /costa-rica/tortuguero/evergreen/ but that returns a 404. I have to manually put /accommodation/evergreen/ into the URL bar to see the post.

This is my first time playing with URLs/permastructs, but this is fundamental to the client, and I'm running out of options (and time).

Any help you can provide would be greatly appreciated!


If it helps, here's the code I had which almost worked, using wp_rewrite:

function add_rewrite_rules(){
    global $wp_rewrite;

    $wp_rewrite->add_permastruct('accommodation', '%country%/%region%/%accommodation%/', false);
    $wp_rewrite->add_permastruct('region', '%region-country%/%region%/', false);

function permalinks($permalink, $post, $leavename){
    $post_id = $post->ID;

    if ($post->post_type != 'accommodation' && $post->post_type != 'region' || empty($permalink) || in_array($post->post_status, array('draft', 'pending', 'auto-draft'))) {
        return $permalink;
    } else if($post->post_type = 'accommodation') {
        $country_object = get_field( 'accommodation_country');
        $region_object = get_field( 'accommodation_region');
        $country = get_post_field( 'post_name', get_post($country_object));
        $region = get_post_field( 'post_name', get_post($region_object));

        $permalink = str_replace('%country%', $country, $permalink);
        $permalink = str_replace('%region%', $region, $permalink);

        return $permalink;

    } else if($post->post_type == 'region') {

        $region_country_object = get_field('region_country');
        $region_country = get_post_field( 'post_name', get_post($region_country_object));

        $permalink = str_replace('%region-country%', $region_country, $permalink);
        return $permalink;

add_action('init', 'add_rewrite_rules');
add_filter('post_type_link', 'permalinks', 10, 3);

This code worked for both CPTs but every page on the site then returned a 404 error. Is there some way I could maybe tweak this and have it working?

  • accommodation_country should have been a taxonomy not a post meta/custom field, there will be a big performance hit because of this. This Q would have been much easier if it was a taxonomy. Note that none of your code touches rewrite rules, and there are no rewrite rule APIs used in that code, post_type_link is not a rewrite rule filter, it is just a final filter for displaying the url of a post in HTML, it has no control over the actual URL
    – Tom J Nowell
    Commented Mar 24, 2021 at 10:23
  • Or better yet, why not set the parent post to the region CPT and let WordPress do it all automagically?
    – Tom J Nowell
    Commented Mar 24, 2021 at 10:28
  • @TomJNowell I'm trying to minimize the amount of work that the client has to do from their end. As they're already populating the country and region fields it made sense to use that information rather than make them do it twice. Regardless, how would I go about setting a Region CPT as the parent of an Accommodation CPT? Commented Mar 24, 2021 at 10:29
  • They shouldn't do it twice, infact the region post meta should be eliminated. A single UI that selects the region CPT, that sets the posts parent ID to a region CPT post. Likewise your region CPTs should do the same but with country CPTs. As for how to do it that would be a good answer for someone to write. Unfortunately I am not an ACF developer so any answer I gave you may be completely useless to you
    – Tom J Nowell
    Commented Mar 24, 2021 at 11:09
  • That sounds great. I looked into it and tried a few things though and I'm not so sure I can figure that out either! What a mess :( Commented Mar 24, 2021 at 12:16


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.