I was wondering how i can remove the prefix from the slugs of custom post types using htaccess rules or any other appropriate method.

Currently my custom post types look like this:


and i would simply like this to be:


I was able to achieve this via a plugin but it significantly added to the page load time..

My common settings in Permalinks is set to "Post Name"


There is a simpler and lighter solution:

First: set the slug argument for your custom post type to '/' when registering the post type:

add_action( 'init', 'register_my_post_type' );
function register_my_post_type() {
    $args = array(
            //The rest of arguments you are currently using goes here
            'rewrite'    => array(
                             'slug' => '/'
     register_post_type( 'my-post-type', $args );   

Second: in preg_get_posts() action hook include your post type in the query when only the $query->query['name'] is present:

add_action( 'pre_get_posts', 'wpse_include_my_post_type_in_query' );
function wpse_include_my_post_type_in_query( $query ) {

     // Only noop the main query
     if ( ! $query->is_main_query() )

     // Only noop our very specific rewrite rule match
     if ( 2 != count( $query->query )
     || ! isset( $query->query['page'] ) )

      // Include my post type in the query
     if ( ! empty( $query->query['name'] ) )
          $query->set( 'post_type', array( 'post', 'page', 'my-post-type' ) );

Third: I've found that in some situations there are conflicts with pages and single posts. I've been able to avoid this conflict by checking if a page with the request name exists and overriding the query conditionals but I'm not sure if more conflict can happens:

add_action( 'parse_query', 'wpse_parse_query' );
function wpse_parse_query( $wp_query ) {

    if( get_page_by_path($wp_query->query_vars['name']) ) {
        $wp_query->is_single = false;
        $wp_query->is_page = true;


IMPORTANT: You need to flush the rewrite rules. You can do it by viviting the permalink options page in the admin area. This is needed because we are registering a new rewrite rule, so the current rewrite rules stored in the database must be regenerated.

  • hmm. I have tested the code but it seems that the whole custom post type disappears when I put its name in your code! – Joanna Mikalai May 19 '14 at 16:59
  • The permalink is correctly changes to www.domain.com/postname but I do not see the link to edit the post nor I can see this at the backend.. – Joanna Mikalai May 19 '14 at 17:04
  • You have to complete the $args for register_post_type() function. To make things easier you can just add the argument 'rewrite' => array('slug' => '/' ) to your actual register post type implementation and then add the code of the second step to your functions.php theme file or to your plugin file. Note that in the second step you have to add the custom post type names in $query->set( 'post_type', array( 'post', 'page', 'my-post-type-1', my-post-type-2', 'etc..' ) );. If you post your actual code to register the custom post types I can post the full working code. – cybmeta May 19 '14 at 18:29
  • ok this works now but it messes up with my pages template sidebar... is it possible to exclude pages from your code? – Joanna Mikalai Jun 1 '14 at 13:18
  • If it works, please accept the answer. I don't understand you "new" problem about pages template sidebar. If you exclude pages from my code, URLs like http://mysite.com/i-am-a-page/ won't work. You will need to load the desired template/sidebar using conditional tags like is_page(), is_single() or `is_singular(). – cybmeta Jun 2 '14 at 10:12

// Well then, first use a function to mention your custom post types.

function my_custom_post_types(){
    return array(

// Then, filtering the request.

    add_action( 'parse_request','my_custom_post_types_parse_request', 999);

function my_custom_post_types_parse_request( $query ){

    if( isset($query->request) )
        $req = explode('/', ltrim($query->request,'/'));
        if( !is_admin() && isset($req['0']) )
            global $wpdb;
            $name = stripslashes($req['0']);
            $_post = $wpdb->get_row( "SELECT * FROM $wpdb->posts WHERE post_status = 'publish' AND post_name ='". $name ."' AND post_type IN ('" . join("', '", my_custom_post_types() ) . "')" );

            if( $_post )
                $query->query_vars['error'] = '';
                $query->query_vars['post_type'] = $_post->post_type;
                $query->query_vars['name'] = $name;

// And next filter all the desired post types link.

add_filter( 'post_type_link', 'my_custom_post_types_permalink', 10, 2);

function my_custom_post_types_permalink( $post_link, $post ){
    if( in_array( $post->post_type, my_custom_post_types() ) && !empty($post->post_name) && $post->post_status == 'publish' && '' != get_option('permalink_structure') )
        $post_name = $post->post_name;
        $post_link = home_url("/$post_name/");
    return $post_link;

EDIT: fixed errors, adding this line due to edit 6 chars minimum requirement

  • I actually have a lot of custom post types namely os_estate, os_villas, os_chalets, os_cottages - could you please modify your code to include all of them? – Joanna Mikalai Dec 9 '13 at 23:12
  • thank you i will test this tomorrow and will let you know how it goes.. I also did notice that i could modify my theme directly.. is your solution in terms of performance and speed adequate? – Joanna Mikalai Dec 10 '13 at 0:40
  • Exactly, you can see i am just using one query to find a match within defined custom post types. But if you use empty slug, that will either return 404 error, or if that returns fine, then post (default post type) page will be inaccessible. But with this solution, we leaving the rest of your site as it is. Thanks. – Shazzad Dec 10 '13 at 22:01
  • That extra query you are using is not needed at all if you include the custom post type in the main query. Also, it is not needed to filter the post_type_link if you set the slug you want to use during the registration of the custom post type. See my answer. – cybmeta May 14 '14 at 9:47

I think that you just need to put rewrite rule in your code in place where you register post type.

Just place this code

$rewrite = array(
    'slug'                => '',
    'with_front'          => true,
    'pages'               => true,
    'feeds'               => true,

after the $labels array();

and before $args = array();

  • 1
    This won't work. If you set the slug = '' it will be filled with the default value which is the post_type. – cybmeta May 14 '14 at 9:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.