Sửa lỗi remove slug from custom post type năm 2024

I recently had a need to rewrite the URLs of all parent and child pages in a custom post type so that they appeared to live at the website root, but in reality, continued to live in a custom post type within their hierarchy.

Preface

The situation:

  1. I have a page called

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    4 that lives at

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    5.
  2. I have a custom post type called

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    4.
  3. I have

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    4 post called

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    8 that lives at

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    9.
  4. I have

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    4 post called

    add_filter('pre_get_posts','custom_pre_get_posts');

    1, that is a child of

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    8, that lives at

    add_filter('pre_get_posts','custom_pre_get_posts');

    3.

The goal:

  1. The

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    4 page should remain where it is i.e.

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    5.
  2. The

    $result = $wpdb->get_row( $wpdb->prepare(

    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',  
    $post_name  
    
    ) ); switch($post_type) { case 'services':
    if ($result->parent_post_name !== '') {  
      $post_name = $result->parent_post_name . '/' . $post_name;  
    }  
    $query->set('services', $post_name);  
    $query->set('post_type', $post_type);  
    $query->is_single = true;  
    $query->is_page = false;  
    break;  
    
    }

    8 post should appear to live at the website root i.e.

    add_filter('pre_get_posts','custom_pre_get_posts');

    7.
  3. The

    add_filter('pre_get_posts','custom_pre_get_posts');

    1 service should also live at the website root i.e.

    add_filter('pre_get_posts','custom_pre_get_posts');

    9.

The reason:

  1. Shorter URLs.
  2. Keep all website pages together.
  3. Keep all services together.
  4. Maintain hierarchy of services.

Setup

I have a custom post type setup like so:

function register_services() {
  $arguments = array(
    'label' => 'Services',
    'public' => true,
    'capability_type' => 'page',
    'hierarchical' => true,
    'supports' => array('title', 'editor', 'page-attributes'),
    'has_archive' => false,
    'rewrite' => true
  );
  register_post_type('services', $arguments);
}

Solution

Remove “services” from the custom post type URL

We’re going to use the

add_action('pre_get_posts','custom_pre_get_posts');

0 filter to do this.

Update 1/19/14: Tweaked function to work with WordPress installed in a subdirectory.

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

  • Line 9: If

    add_action('pre_get_posts','custom_pre_get_posts');

    1 is a valid post object.
  • Line 13: If post belongs to a post type that we want to remove the post type slug from.
  • Line 15: Build a new permalink from home URL with just the post name.

All of this will essentially give us URLs like this:

  • add_action('pre_get_posts','custom_pre_get_posts');

    2
  • add_action('pre_get_posts','custom_pre_get_posts');

    3

But neither of those will work – you’ll get a 404 error – because WordPress can no longer identify those posts as

$result = $wpdb->get_row(
  $wpdb->prepare(
    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',
    $post_name
  )
);
switch($post_type) {
  case 'services':
    if ($result->parent_post_name !== '') {
      $post_name = $result->parent_post_name . '/' . $post_name;
    }
    $query->set('services', $post_name);
    $query->set('post_type', $post_type);
    $query->is_single = true;
    $query->is_page = false;
    break;
}

4 posts and therefore attempts to find pages called

$result = $wpdb->get_row(
  $wpdb->prepare(
    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',
    $post_name
  )
);
switch($post_type) {
  case 'services':
    if ($result->parent_post_name !== '') {
      $post_name = $result->parent_post_name . '/' . $post_name;
    }
    $query->set('services', $post_name);
    $query->set('post_type', $post_type);
    $query->is_single = true;
    $query->is_page = false;
    break;
}

8 and

add_filter('pre_get_posts','custom_pre_get_posts');

1, which don’t exist, hence the 404 error.

If you take a look at WordPress’ rewrite rules:

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

You’ll see something similar to this:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

As you can see, using the

add_action('pre_get_posts','custom_pre_get_posts');

7 slug, WordPress knows to take whatever comes after it (

add_action('pre_get_posts','custom_pre_get_posts');

  1. and pass it in as the

add_action('pre_get_posts','custom_pre_get_posts');

9 value (

$post_name = $query->get('pagename');

0), which translates to something like:

$post_name = $query->get('pagename');

1

Since the first rewrite rule no longer applies (slug removed), it now passes

$post_name = $query->get('pagename');

2 in as the

add_action('pre_get_posts','custom_pre_get_posts');

9 without specifying the post type, and since this page doesn’t exist, WordPress can’t return the page.

Add post type back to the post query

Now that WordPress doesn’t know that it’s dealing with a custom post type, we need to provide it with this information. For that we’re going to use the pre_get_posts hook.

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

  • Line 7: We’ll need the WordPress database object to get additional information about the post.
  • Line 9-11: We’re going to make sure that the current query is the main query. Since a page can have multiple queries, we’re limiting our filter to only the query that gets information about the page itself.
  • Line 13: We’re going to grab the

    add_action('pre_get_posts','custom_pre_get_posts');

    9 from the query, which might be

    $post_name = $query->get('pagename');

    2 or

    $post_name = $query->get('pagename');

    6.
  • Line 15-20: Now we’re going to use the

    add_action('pre_get_posts','custom_pre_get_posts');

    9 to look in the database for that particular post and extract its post type.
  • Line 22-23: If this post has a post type that we know we need to manipulate, we’ll need to perform additional actions.
  • Line 24: We need to a create a query var for the post type and set it to the

    add_action('pre_get_posts','custom_pre_get_posts');

    9, so WordPress knows where to actually find the data for the post.
  • Line 25: We also need to set the

    $post_name = $query->get('pagename');

    9 back to

    add_action('pre_get_posts','custom_pre_get_posts');

    7.
  • Line 26: In order for WordPress to load the right template i.e.

    $post_name = $query->get('name');

    1, we need to set

    $post_name = $query->get('name');

    2 to

    $post_name = $query->get('name');

    3.
  • Line 27: Last, but not least, we set

    $post_name = $query->get('name');

    4 to

    $post_name = $query->get('name');

    5 for good measure, since it’s a custom post type.

Update 9/20/13: Changed

$post_name = $query->get('name');

6 to

$post_name = $query->get('name');

7

Update: 12/23/14: As of WordPress 4.0, the code above doesn’t work. The following changes need to be made:

$result = $wpdb->get_row(
  $wpdb->prepare(
    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',
    $post_name
  )
);
switch($post_type) {
  case 'services':
    if ($result->parent_post_name !== '') {
      $post_name = $result->parent_post_name . '/' . $post_name;
    }
    $query->set('services', $post_name);
    $query->set('post_type', $post_type);
    $query->is_single = true;
    $query->is_page = false;
    break;
}

Result

You’ve met all your goals. In addition, if you try to create a page with the same name as one of your services, you’ll notice that the

$post_name = $query->get('name');

8 (i.e.

$post_name = $query->get('name');

9,

add_action('pre_get_posts','custom_pre_get_posts');

9, etc.) will now increment, avoiding a possible collision.

Here are a couple of things to keep in mind:

  1. I didn’t test the performance of this yet, but if you use some kind of caching system on your website, you should be fine.
  2. All my functions live within a PHP namespace, so I only prefixed them with

    add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') {

    return $permalink;  
    
    } switch ($post->post_type) {
    case 'services':  
      $permalink = get_home_url() . '/' . $post->post_name . '/';  
      break;  
    
    } return $permalink; }

    01 for simplicity of this post.
  3. I’ve looked at many different solutions online, and they all had their fair share of problems, so this may too, I just don’t know it yet.

If you have questions or suggestions, leave them in the comments below.

Featured image by Eilis Garvey.


Previously posted in WordPress and transferred to Ghost.

Tristan C May 29, 2013 at 9:15 am

Thanks for the post! Unfortunately I can’t get it to work… I keep on getting 404s. Have tried updating the permalinks,

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

02, etc.

Ryan Sechrest May 29, 2013 at 12:50 pm

First, I would output the current WordPress rewrite rules (example in post), just to make sure the ones you added are indeed active. Second, I would print out the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

03 variable (

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

  1. in

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

05 to see that the values are set to what you expect (and also make sure the code in the filter is executed).

Marco Panichi July 7, 2013 at 8:34 am

Hi Ryanm thank you very much for this article. I’m going mad with this slug!!! I’ve tried your solution but it doesn’t work. Maybe you can help me – I have this situation:

– a custom type called “static” – it’s hierarchical – my permalink structure is: “/blog/%postname%” – WordPress 3.5.2

Following your instruction everything works in admin side: I can create a “static” page called “My Page Title” and the permalink in the editor becomes “http://www.—.com/my-page-title”

Unfortunally, in the website I can reach this page only with the URL “http://www.—.com/blog/static/my-page-title”

I have tryed lots of solutions for DAYS!

Ryan Sechrest July 11, 2013 at 8:36 am

When you go to the permalink that it’s supposed to be, what do you get? A page not found?

GG July 8, 2013 at 3:34 am

Man… as much as I want this to work its just not happening.

I’m trying to use this in combination with 301 redirects to create a short urls post type. I didnt have the postype setup to begin with, so it took a minute to figure out I needed to add:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

06 May want to note this in the post.

Also, setting

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

07 to

$post_name = $query->get('name');

3 automatically adds it to your permalink structure, you show

$post_name = $query->get('name');

5.

My custom permalink structure is set to:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

10 So I was getting

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

11 Shouldnt have mattered because you’re grabbing the last part of the path, but I think something was screwy there because when I print

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

12 later in

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

05, it wasn’t finding anything.

Last resort, i think ill use http://wordpress.org/plugins/custom-permalinks/ and change them manually. Thanks for trying though! Glad it worked for you.

Ryan Sechrest July 11, 2013 at 8:48 am

Let’s systematically troubleshoot this. For step

1, let’s confirm the

add_action('pre_get_posts','custom_pre_get_posts');

0 filter is working. If you go anywhere in your template and use

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

15, with

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

16 belonging to a

add_action('pre_get_posts','custom_pre_get_posts');

7 post, please provide the absolute path it printed out (you can omit the domain).

David July 14, 2013 at 11:43 pm

To get this to work I had to change:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

18 to:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

19

AJ Clarke July 21, 2013 at 5:24 pm

Works great, thanks man!

kevin July 30, 2013 at 8:16 pm

What are your thoughts about getting things running with WPMU, as of right now it would default the URL back to

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

20 rather than

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

21

Cheers kc

Ryan Sechrest July 31, 2013 at 9:30 pm

You have a couple options.

(1) You could wrap the filters in a

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

22 check, which would only apply it to your main site, leaving your child sites as-is.

(2) In step 1, where you remove the “services” slug, you could perform a

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

23 check and then parse

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

24 accordingly, essentially accounting for the site name by keeping it in there or putting it back.

Tomas August 7, 2013 at 8:36 am

Will this work with WPML, too? I’ve tried this plugin http://wordpress.org/plugins/remove-slug-from-custom-post-type/ but it only removes the slug for the main language…

Ryan Sechrest August 7, 2013 at 8:55 am

I do have MU enabled on the site that I did this on, however, I’m only applying this code on the main site and not child sites, since don’t need it (yet). Looking at the code above, there is nothing to suggest that it wouldn’t work, except for, as Kevin pointed out, you may have to adjust the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

25 that’s created in step

1 when MU is installed as sub-directories instead of sub-domains, since there’ll be an extra component in the URL that needs to be accounted for.

Stefan September 19, 2013 at 2:38 am

Ryan, your tutorial was very useful. My case was similar to yours and I was able to remove the slug. But I made a few modifications for this to work as you can see here https://gist.github.com/stefanbc/6620151. First of all, this line

add_filter('pre_get_posts','custom_pre_get_posts');

should be something like this

add_action('pre_get_posts','custom_pre_get_posts');

because according to the WordPress codex there is no filter pre_get_posts And another thing I modified was this

$post_name = $query->get('pagename');

to this

$post_name = $query->get('name');

This was my case. Thanks for the awesome tutorial.

Ryan Sechrest September 20, 2013 at 10:03 pm

Thanks for the feedback, Stefan. You are right about it being an action and not a filter. I’ve updated my post above. With regard to

add_action('pre_get_posts','custom_pre_get_posts');

9 vs

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

27, if your custom post type is based on a post, which is the default,

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

27 would be in fact the way to go, but if it’s based on a page, I think

add_action('pre_get_posts','custom_pre_get_posts');

9 is recommended. I’m actually using it as a page, but omitted that in my setup– thanks for catching that!

Dennis October 13, 2016 at 11:49 am

@stefan Your github code worked except that it was throwing a php error regarding variables. I changed:

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

0

to

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

1

and it worked beautifully. Thanks

chụp ảnh cưới October 12, 2013 at 4:25 am

I using

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

2

with htaccess?

Ryan Sechrest October 12, 2013 at 3:58 pm

The rewrite rules I showed were only a subset of those in the system to explain that particular point. What you have works just as well.

Sam October 30, 2013 at 8:21 am

Sorry for asking – I couldn’t get where do I make the changes :$ Help? O:-)

Ryan Sechrest October 30, 2013 at 8:38 am

You could put this in your theme’s

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

30 file, however it would probably be best suited in a plugin. Take a look at the WordPress codex on how to create a plugin.

efix December 11, 2013 at 4:30 pm

Hi,

Can I use your way for post-type pages also? I want to rewrite my URL structure from http://www.ex1.com/parentpage/childpage to http://www.ex1.com/childpage. So your code should work for me to. But it didn’t. Any idea why?

thank you

efix

Ryan Sechrest December 12, 2013 at 2:40 am

This code will not work on native post types, only custom post types.

Victor December 30, 2013 at 3:59 pm

here is how i did:

register_post_type with these args

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

31

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

32 using this approach there is no need to use

add_action('pre_get_posts','custom_pre_get_posts');

0 filter then comes

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

05

Ryan Sechrest December 30, 2013 at 9:27 pm

Thanks for sharing, Victor. I’ll try that out next time I’m working with that code!

Bill January 12, 2014 at 10:55 am

Great article. THis seems to work for me on my sandbox site except one thing….

I have a main wordpress website at http://www.website.com, but I have an additonal wordpress install in a folder of the main website….so in other words, I have my live site here: http://www.website-name.com but I installed another wordpress install in a folder I named DEMSO off that live site I use for sandbox stuff. http://www.website-name.com/demos/SANDBOX-SITE

I never have any problems, however when I am trying to use this code on the sandbox site, when I view the custom post type, it takes me back to the root wordpress install, so instead of the custom post type url being http://www.website-name.com/demos/SANDBOX-SITE/post-name (the CPT slug is gone…yeah!!) it is http://www.website-name.com/post-name

Can you recommend the code alteration so it doesnt strip it all the way back to the main website root, and instead takes it back to that particular wordpress install’s root?

http://www.website-name/demos/

Ryan Sechrest January 13, 2014 at 2:38 pm

Bill, I revised the function to be a little more flexible and it should also now work with WordPress installed in a subdirectory. Take another look at the code in step 1.

Bill January 18, 2014 at 7:02 pm

Thanks for the reply Ryan. I must be doing something wrong, because I cant get it to work. First, in step 1, you forgot the word

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

35 in the function. Line 8 should be:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

36

But regardless, I tried this but it didnt work. Not sure if this matters or not, but the site is located in this path

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

37

The custom post type name is “neighborhoods”. Here is my code:

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

3

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

4

Ryan Sechrest January 19, 2014 at 5:36 am

You’re right, I didn’t test my quick update and it contained an error. Please take another look, this time tested, and it should now work for you.

Bill January 19, 2014 at 6:43 am

Hi Ryan…

Ok, I tried new code …the permalink does change int he editor screen …the slug is not there, but I get the 404. I tried resetting permalinks just to make sure there wasn’t an issue there with the 404, but still no dice.

I will paste what I have below for the CPT just in case it is something on my end…

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

5

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

6

Ryan Sechrest January 19, 2014 at 6:34 pm

Here is the code I used for testing https://gist.github.com/ryansechrest/03b3caa9f12c2d70f7bc. Throw this code in

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

38 and create a parent and child

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

39 post. Let me know if you have the same issues as with your

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

40 post type.

bill January 21, 2014 at 8:05 am

thanks Ryan. I followed your instructions…I still get a 404 for both service posts I create (one parent and one child)

Ryan Sechrest January 21, 2014 at 12:24 pm

Maybe you have a different kind of WordPress configuration or a plugin that is preventing this from working? I downloaded WordPress 3.8, installed it in a subdirectory as a single site, and then only installed that plugin I referenced above, and that worked for me.

As a side note, this is also working on a production site running multisite, where WordPress is in a subdirectory, but served from the root.

The ultimate test would be to setup a clean WordPress, just to rule out a configuration/setup/plugin issue. If that works, you know it has something to do with your sandbox.

With regard to debugging the sandbox, since the problem seems to be related to step

2 (since the URL seems to be accurate), I would start with printing out various variables. Something like:

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

7

Bill January 21, 2014 at 1:05 pm

Thanks for your help Ryan!

….I deactivated all plugins, and switched to 2014 theme…. still same issue. 404 error.

Here is my set up: I originally had a website in the main public_html folder. Then over the years, I added three add-on domains for this hostgator account. so it was set up like this:

public_html/MAIN WORDPRESS INSTALL/ (then I have three different folders here….one for each of the three add-on websites)
Each of the three folders has its own WordPress install. Then a year or so ago, I deleted the main wordpress install from the server because I dont use that site anymore…. but I kept the three ass-on websites….. so it looks like this

public_html/website1.com or public_html/website2.com or public_html/website2.com

It’s the same

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

41 but with three different add-on domains, and the original wordpress install is not there anymore….just the addon folders. Not sure why this would make a difference, but maybe it does?

Ryan Sechrest January 21, 2014 at 1:33 pm

The individual WordPress sites (using add-on domains) should be isolated from each other, so I don’t think that’s the problem.

Re: WordPress theme and plugin deactivation, while that is a good test, there could still be some configuration in the database that could prevent this from working. A better test would be a fresh install with a newly setup database.

Also, have you checked the output of the variables mentioned in my previous comment?

Daniel January 30, 2014 at 9:21 pm

Hi Ryan,

Nice job. However I hope you can help. I’m wondering what if you want to replace the post type slug with my taxonomy term slug. How do I do that?

Cheers.

Ryan Sechrest January 30, 2014 at 10:49 pm

You could try something like this. It assumes you are using the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

42 taxonomy and only have one term. If you have more than one term, it will use the first one. In addition, while the permalink will generate based on your selected term, it doesn’t validate the term when parsing the URL, meaning the page would render with any term, really.

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

8

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

9

Yumna March 11, 2014 at 12:43 am

Hi Ryan, I am trying to change my cpt urls using your code. I could remove slug from parent post urls, but it fails on child post urls. My cpt is hierarchical. I modified the post_type_link hooked function as below and now it changes link for child pages as well

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

0

Now the permalinks are created correctly, but the problem is that i get a 404 on child posts. I have dumped the query on both parent and child posts and i have noticed that on child post, the query_var attachment is set and no other query_vars are set. Please help if possible. Thanks.

Ryan Sechrest March 12, 2014 at 9:47 am

OK, first you’ll want to change

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

43 to

$post_name = $query->get('name');

9 on line 10, because otherwise you might have space and capitalization issues in the URL. I’d also add the trailing slash.

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

1

Second, and this is the main problem, the post name being used to find your child page in the database contains the parent slug, in other words,

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

45, but there is no post with a post name like that, hence the page not found error.

That means you have to strip out the parent slug or simply take the contents after the last slash.

If you add the following after

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

46 in the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

47 hook, it should work:

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

2

Let me know if this works.

Irvin April 29, 2014 at 2:50 am

Hi Ryan,

I may need help on this: http://pastebin.com/Qwaf8wuD

The Parent works well actually but the Child pages are giving me 404: domain.com/parent1/weather domain.com/parent2/weather domain.com/parent2/weather

anything did I miss?

Ryan Sechrest April 29, 2014 at 9:12 am

I don’t think you followed my tutorial very closely, because your code looks completely different. At first glance, though, I believe you’re coming up empty here:

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

3

Try swapping

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

48 with

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

49.

Yana January 13, 2015 at 6:51 am

Hello Ryan,

thank you for this great post, it was exactly what I am looking for.

Just one question regarding to Tomas post if this function or plugin will work with WPML:

I use your function for a site which is published in two languages (German source and English using WPML plugin). I have different custom post types (on of them is members, not hierarchical) which are first filled in an overview page (simple loop) including an link to the detail page of the member. The overview works as expected but when I try to get the detail page it only works in the German version, the English version throws an error 404. I would really appreciate any advise which will point me to the right direction.

Thank you, Yana

Ryan Sechrest January 13, 2015 at 9:55 am

Looking back at Tomas’ post, I didn’t even realize that he was talking about WPML the plugin and not WPMU… I skipped right over that!
"I use your function for a site which is published in two languages (German source and English using WPML plugin)."
So your main site is

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

50 and your translated site is something like

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

51, right?
"I have different custom post types (on of them is members, not hierarchical)"
So member pages look something like this, right?

*

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

52 *

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

53 *

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

54
"which are first filled in an overview page (simple loop) including an link to the detail page of the member"
When you say overview and detail page, do you mean something like:

Overview:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

52 — Contains picture, name, basic info.

Detail:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

56 — Contains expanded profile, posts published, etc.
"The overview works as expected but when I try to get the detail page it only works in the German version, the English version throws an error 404."
So, this works:

*

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

52 *

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

58 *

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

56

But this does not:

*

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

60

Did I understand the problem correctly?

Yana January 13, 2015 at 2:04 pm

Hello Ryan, thank you for your response.
"ple.org/?lang=en, right?"
No it is

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

61 and for german (source language) only

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

62

My german overview page has this url format:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

63 with a list of all members.

It is a loop through the cpt (in the template file) with listing the members in this page showing their picture and small informations, including a link to their detail page.

The english version has this url:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

64

What I do is using your function to remove the slug from cpt an add a new path to get the following result for the detail page in the url (german):

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

4

and for the english version:

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

5

I have an if statement in the function to get the current language code and use to two different path for the german and english version.

Everything works perfect in the german version but when I try to call the detail page of e.g.

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

65 I get the error 404.

Thank for your help and if you need more information please let me know.

Yana

Ryan Sechrest January 13, 2015 at 2:52 pm

You have

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

66 several times in the URL, but that’s just to indicate that it is an English slug, right? The only thing used to determine language is

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

66 right after the domain, correct?

In other words, this works:

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

6

But this does not work:

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

7

Did I get that right? If so, it seems part 1, building the URL, is working fine for you, but you’re having trouble with part 2, which is rebuilding the query to then display the resulting data.

Yana January 14, 2015 at 4:47 am

Hello Ryan, yes, exactly. This is working

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

6

This is not working

echo '
' . print_r(get_option('rewrite_rules'), true) . '
';

7

Thank you Yana

Ryan Sechrest January 14, 2015 at 9:05 am

OK, can you post the code you’re using for part 2 with the modifications you made?

Yana January 14, 2015 at 9:33 am

Here is the code I am using:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

0

And the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

68 functions look like this:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

1

It seems that the first function causes also an error in the backend, all page, post and cpt linstings disappeared this morning. By removing the function everthing works again.

Ryan Sechrest January 14, 2015 at 2:21 pm

@Yana

Can you print out

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

69 on a German and an English error page? You can do that with something like:

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

70

"It seems that the first function causes also an error in the backend, all page, post and cpt linstings disappeared this morning. By removing the function everthing works again."
Do you know what the error was?

Yana January 15, 2015 at 8:05 am

I was wrong, it is not the first function, it is the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

68 function which causes the problem. After cache clearing the whole site throws an 404 error. It seems that in my case the first cpt

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

72 is allways used in every query.

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

2

Ryan Sechrest January 16, 2015 at 9:00 am

Give the original code a try for

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

47 (especially the query part that gets the post type), since you are not exactly doing what I’m doing in the blog post (which is writing all permalinks to the root without the post type slug).

Trieu To February 2, 2015 at 6:46 am

Hi Ryan

I use your code but at step 2 it not working.

This is my code:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

3

Trieu To February 2, 2015 at 8:01 am

Dear Ryan I want to few character to url example:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

4

But step 2 not work. How can i fix it? and How can i add any character to my custom post type slug?

Ryan Sechrest February 3, 2015 at 9:01 am

Let’s say your post name is “foobar” and let’s say you build a URL as follows:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

5

When the query is performed to look up the post, there is no post in your database called “examplefoobar,” which is why step 2 is not working. If you are adding “example” in the post name, you must remove it again prior to making the query. So, on line 8 in your first post, you could try something like this:

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

6

Note that in this case, regardless of where “example” occurs in the string, it will be removed, so depending on your rules, you may want to refine it.

Better yet, though, if you changed it to

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

74 instead of

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

75, that might be a little bit more reliable, as you could just grab everything after the last slash, but at that point, you might as well just change the slug of the post type to

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

76, because then you don’t need any of this extra code.

You can find this argument in the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

77 section on the page in the WordPress codex.

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

7

Teilmann February 16, 2015 at 2:24 am

Hello Ryan, I have implemented your solution on my local environment, and it works like a charm. However, when i do the same on the online solution, some links break, and i cannot save stuff in the administration panel. (posts, permalinks and more). Any thoughts?

Teilmann February 16, 2015 at 3:00 am

Nevermind Ryan, i seemed to fix the problem. The former programmer on the site im working on, made a bunch of whitespaces in the online version of the functions.php file, which caused the white screen of death. thanks for the awesome solution

Ryan Sechrest February 16, 2015 at 9:56 am

Good deal– glad it’s working for you!

Thomas Teilmann February 17, 2015 at 2:13 am

Hello again, it seems i have found yet another problem :/

When i implemented your solution, i changed the permalinks to some of my custom post types, so that they linked to the root of the website. By doing that, i now have more than one link to my custom post types. The links i had before i changed it, is still active, and thats not good according to google (SEO), because the pages are interpreted as duplicates (obviously).

Any idea how to fix this?

Pierre May 28, 2015 at 4:48 am

Hi ryan, Trying to use your solution to remove “product” from slug in woocommerce But the query in get is quite empty (at least for name and pagename) , and i got 404.

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

8

This is the code, in case you spot any issue…

khubbaib August 29, 2015 at 3:17 pm

You are rock ryan… Its help me alot,,, In first try it was not working but now it working… Thank you so much…

Colir November 12, 2015 at 9:57 am

Hi.

DO you think is possible to just remove the cpt name from a slug, but keep the hierachy url ?

ex: http://www.domain.com/cpt-name/parent-post/child-post to http://www.domain.com/parent-post/child-post

Your code work well, but as you need, also remove the hierachy

thanks

Colir November 13, 2015 at 7:06 am

Hi. Thanks for this solution.

However, i can’t get a solution to keep the parent post name in the url ex: http://www.domain.com/cpt-name/cpt-parent-page/cpt-child-page to : http://www.domain.com/cpt-parent-page/cpt-child-page any idea ?

thanks a lot

Ryan Sechrest November 13, 2015 at 12:30 pm

Hi Colir, Take a look at this Gist I put together– it should do the trick.

colir November 16, 2015 at 4:24 am

i think i‘ve found a first probleme

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

78 return an empty string…

Colir November 16, 2015 at 4:10 am

Thnaks a lot for your answer.

The url is good: http://www.domain.com/parent_cpt_page_name/child_cpt_page_name

However i have a 404 error on my child page.

I create my CPT with the CPT UI plugin (so i remove your cpt definition in your plugin)

my CPT name is ‘galerie’ so i also remove all ‘service’ string by ‘galerie’ in you plugin.

Colir November 16, 2015 at 7:36 am

Yo Ryan

i’ve spent time on the problem and here is what happen : – i can’t get the post type in the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

47 function because after apply the

add_action('pre_get_posts','custom_pre_get_posts');

0 filter,

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

81 is empty.

So here is what i’m done. this work but it’s really tricky

Array
(
  [services/(.+?)(/[0-9]+)?/?$] => index.php?services=$matches[1]&page=$matches[2]
  [(.?.+?)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
)

9

note: i also test the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

82, because my home page is a custom template page. on this page (home), the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

12 is also empty, but not his ID.

How to you think we can improve the things ? thanks

Ryan Sechrest November 16, 2015 at 9:02 am

Hi Colir,

Print out

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

03 in

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

47 using something like:

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

0

And let me know what the result is.

You need to obtain the post type, otherwise your hook will fire on every request and for all post types, which will most likely break other aspects of WordPress.

Also, just to confirm, are you running the latest version of WordPress? I tested this with version 4.3.1 and a non-multisite install.

Colir November 17, 2015 at 7:18 am

Here is the result. For info, my child post name is CEP. As you can see the query let appear it as an attachment, but i didn’t know why (this is not the case if didnt filter the permalink). My WP version is 4.3.1

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

1

Ryan Sechrest November 18, 2015 at 1:22 am

Hi Colir,

I see the attachment, and that doesn’t appear to be normal. My guess is that either your theme or a plugin is responsible for that. It’s difficult to troubleshoot without having more context. I ran my code in a brand new WordPress installation, so I know there was nothing interfering there.

That said, I would test the code in a brand new WordPress installation and then add your theme and plugins back, one by one, to isolate what might be causing this.

Colir November 18, 2015 at 9:11 am

Ok. Thanks a lot for your help. I will try to setup a news WP and see

Colir November 18, 2015 at 9:25 am

Ryan, i‘ve just setup a new clean install of the last version of WP,

and active only one plugin ‘Custom Post Type UI’. I’ve setup a new cpt with the hierachie param on.

I’ve the same result as you can see

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

2

Colir November 18, 2015 at 9:38 am

I’ve made a new test :

removing custom Post Type UI and put directly your gist…same effect… The cpt

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

39 is seen as an attachment

Ryan Sechrest November 20, 2015 at 12:07 am

Hi Colir, Can you confirm where you placed the statement in the code and which page you were trying to load when it printed that out?

Colir November 20, 2015 at 7:20 am

I place it in the “pre_get_post” and i try to load a child page of my cpt

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

3

Ryan Sechrest November 20, 2015 at 8:27 am

Hi Colir, Try putting it right before:

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

4

Also, when I was testing, I was using the Twenty Fifteen theme– which one were you using?

Colir November 24, 2015 at 1:39 am

Hi Ryan.

First at all thanks a lot for the timing you spending to help me.

So it’s really stange. I’m also woth the twenty Fifteen theme.

After putting the die just before $query, this work well. So i ‘ve donwload Custom Post Type UI, set another type and change the Git.

After this done, any thing work as except. I’ve got an 404 on my custom type And for the cpt “Services” (which work well before), i’m falling with the ‘attachment problem’ even if i remove the CPT plugin…

sorry for my poor english i’m french. thanks a lot

Colir November 24, 2015 at 1:45 am

Here the modified git i use

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

5

Ash Bryant April 6, 2016 at 7:15 am

Hi Ryan,

I’ve been trying to get this to work and in hope to find a solution, I also came across this before yours ( http://kellenmace.com/remove-custom-post-type-slug-from-permalinks/ ) which works, but not on a multisite install it would seem ( just on the main site ), so I continued to look for help and came here. I saw that you said your solution should work on a multisite install, so I thought ‘great!’, but I’ve been trying to get this to work for a little while now, and keep getting a 404 error.

I have a series of cpt’s that I want to do this with ( ‘excursion’, ‘hotel’, ‘offer’, ‘tour’ ), I have these setup on a multisite install of WP using subdomains. I have also got domain mapping on them, but I have tried your solution first without this active, but with no joy.

Can you help?

CODE

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

6

Ryan Sechrest April 6, 2016 at 9:29 am

Hi Ash, Does my code work for you on the first site in a multisite environment and just not on the other sites, or does it not work at all?

Ash Bryant April 7, 2016 at 2:55 am

Hi Ryan,

It doesn’t seem to work at all. On the main site the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

87 function seems to work as the url has those parts removed. But the

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

05 function doesn’t seem to work as I get a 404.

On the other sites of the install it doesn’t do anything at all, the pages load without removing the parts and therefore load as ‘normal’.

Ryan Sechrest April 7, 2016 at 10:30 am

Just to be sure, can you visit the Permalinks page to flush your rewrite rules and then see if you still get a 404?

Ash Bryant April 8, 2016 at 7:30 am

Yep already done that :/

Ash Bryant April 8, 2016 at 8:58 am

If it helps these are my CPT & Taxonomies

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

7

Ash Bryant April 8, 2016 at 9:28 am

I’ve tried adding

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

89

but it doesn’t show anything, but if I change your code to match Colir’s version here …

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

8

it returns as banner, so is it the query here that is the issue?

Ryan Sechrest April 8, 2016 at 4:18 pm

Hi Ash, I took your code, with a minor tweak in labels:

add_filter(
  'post_type_link',
  'custom_post_type_link',
  10,
  3
);
function custom_post_type_link($permalink, $post, $leavename) {
  if (!gettype($post) == 'post') {
    return $permalink;
  }
  switch ($post->post_type) {
    case 'services':
      $permalink = get_home_url() . '/' . $post->post_name . '/';
      break;
  }
  return $permalink;
}

90

You’re missing a comma after the

$post_name = $query->get('name');

3 before

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

92.

Then I took all of my code above, with the revised version from 12/23/14, which basically is this:

add_action(
  'pre_get_posts',
  'custom_pre_get_posts'
);
function custom_pre_get_posts($query) {
  global $wpdb;
  if(!$query->is_main_query()) {
    return;
  }
  $post_name = $query->get('pagename');
  $post_type = $wpdb->get_var(
    $wpdb->prepare(
      'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
      $post_name
    )
  );
  switch($post_type) {
    case 'services':
      $query->set('services', $post_name);
      $query->set('post_type', $post_type);
      $query->is_single = true;
      $query->is_page = false;
      break;
  }
}

9

I pasted it into a functions.php file on a multisite I’m running, and added multiple tours to two different sites. Each permalink had the post type removed and I saw each tour page render (using the default template). There must be something else you have tweaked or installed that is interfering with the code above, but based on what you posted, I can confirm that it works.

Ash Bryant April 15, 2016 at 10:42 am

Odd. Could domain mapping be causing the issue do you think?

Ryan Sechrest April 15, 2016 at 11:10 am

Is that a plugin or are you doing it manually? The site network I tested it on actually uses a top-level domains for each site (as apposed to subdomain or subdirectory), so that does work as well.

Ash Bryant April 15, 2016 at 11:30 am

Bugger.

It’s a plugin, this one https://wordpress.org/plugins/wordpress-mu-domain-mapping/

If it’s not that, then the only other thing I can think of that might be causing the issue is the WPML plugin that allow the client to translate their website.

Ryan Sechrest April 15, 2016 at 2:17 pm

I could definitely see WPML causing issues, since it manipulates database queries. I run a network of sites with WPML, but I don’t remove post type slugs on any of those sites. You might deactivate both the domain mapping and WPML plugin to see if that resolves the issue. If so, then at least you know where to look to find a workaround.

Alex April 26, 2016 at 9:50 am

Hi, Ryan.

Thank for you post. It’s very useful. But I was faced with another problem. I want to change woocommerce product url. Page returns a 404 error, and in

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

93 Object object i have

$result = $wpdb->get_row(
  $wpdb->prepare(
    'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',
    $post_name
  )
);
switch($post_type) {
  case 'services':
    if ($result->parent_post_name !== '') {
      $post_name = $result->parent_post_name . '/' . $post_name;
    }
    $query->set('services', $post_name);
    $query->set('post_type', $post_type);
    $query->is_single = true;
    $query->is_page = false;
    break;
}

0

No relevant information. What it can be? It looks like the function is executed after construction of the post. I would be grateful for any advice

Ryan Sechrest April 26, 2016 at 11:38 am

Hi Alex! I don’t use WooCommerce, so I don’t have any information that would be helpful here. I will say that my tutorial is for changing the URL for post types that you register. Changing the URLs in post types registered by a third-party plugin might have unintended consequences.

Ajay January 19, 2017 at 1:47 pm

Awesome. One of the few solutions that work to get the URL’s right !!!! Thank you

Leo February 11, 2018 at 8:54 am

Hi Ryan,

I am using WP 4.9.2 and I had to change:

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

94 to

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

95

because the first one was always empty. Maybe it could help somebody…

Viktor Ormanow April 3, 2020 at 1:18 am

Yeah, same issue on WP. Plug-ins do help but slow down the page speed.

Forbes Robertson February 23, 2021 at 9:02 am

Greetings!

I hope that someone still keeps an eye on this post.

I have implemented the code above and I am able to rewrite the URL from

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

96 to

add_filter( 'post_type_link', 'custom_post_type_link', 10, 3 ); function custom_post_type_link($permalink, $post, $leavename) { if (!gettype($post) == 'post') { return $permalink; } switch ($post->post_type) { case 'services': $permalink = get_home_url() . '/' . $post->post_name . '/'; break; } return $permalink; }

97

I gotta say this works excellent.

I am running into a problem. Everything works fine if I initially publish a post… the slug gets create and saved and the post is viewable.