How to Remove Slugs for a Custom Post Type in WordPress

So, back in the day, not to long ago, one could remove slugs in a CPT(Custom Post Type), simply by adding the following line to the arguments array when calling the register_post_type function:

'rewrite' => array('slug' => ''),//NOT THE SOLUTION!

However, that no longer works as of WordPress version 4.7.?, so I put together the following two function from various other solutions I've seen. This solution works for a single post type or multiple post types. I am publishing it because I haven't seen any elsewhere that could handle more than one CPT at a time. I wrote this to remove slugs for two CPTs, but you could us it for one or as many as you want, the two functions are as follows and can be added to functions.php or, as I do, to a plugin file, below the register_post_type functions:

<?php

//Two functions to remove slug from team_member and event CPTs:
function remove_evil_slugs($post_link, $post, $leavename) {

    if('team_member' != $post->post_type || 'event' != $post->post_type ||'publish' != $post->post_status) {
        return $post_link;
    }

    $post_link = str_replace('/' . $post->post_type . '/', '/', $post_link);

    return $post_link;
}
add_filter('post_type_link', 'remove_evil_slugs', 10, 3);

function  parse_evil_slugs($query) {

    if(!$query->is_main_query() || 2 != count($query->query) || !isset($query->query['page'])) {
        return;
    }

    if(!empty($query->query['name'])) {
        $query->set('post_type', array('post', 'team_member', 'event', 'page'));
    }
}
add_action('pre_get_posts', 'parse_evil_slugs');

?>

Those are the two functions that accomplish the goal. Simply replace all occurrences of "team_member" and "event" with the names of your own custom post types. If you use any rewrite rules in the args parameter for the register_post_type function, comment it out. For example if you see anything that looks like this:

'rewrite' => array('slug' => ''),

or

'rewrite' =>false,

...either comment it out of delete it all together from the function that creates the custom post type(CPT).

Update 3-27-2017

I've noticed that on some servers, the above solution no longer works, so I was able to program a new solution for NGINX servers. If your server is not an NGINX server, I found a solution here that should work for all even though it is a bit outdated: https://github.com/jonbish/remove-slug-from-custom-post-type. The code below is code I wrote based on that link but updated for nginx servers:

<?php
class JAFTY_CPT_SlugKiller{
    
    static $_s = null;
    private $htaccess_tag = 'SLUG KILLER REMOVE SLUG RULES';
    
    public function __construct() {
        $this->rewrite_rules();
        
        add_action('wp_insert_post', array(&$this, 'post_save'));

        add_filter('post_type_link', array(&$this, 'remove_slug'), 10, 3);
        
    }
    
    
    static public function init() {
        if (self::$_s == null) {
            self::$_s = new self();
        }
        return self::$_s;
    }
    
    static public function flush_rewrite_rules() {
        $jafty_o = self::init();
        $jafty_o->rewrite_rules(true);    
        //$jafty_o->add_rules_htaccess();
    }

    public function post_save($post_id) {
        global $wp_post_types;
        $post_type = get_post_type($post_id);
        foreach ($wp_post_types as $type=>$custom_post) {
            if ($custom_post->_builtin == false && $type == $post_type) {
                $this->rewrite_rules(true);
                //$this->add_rules_htaccess();
                flush_rewrite_rules();
            }
        }
    }
    
    public function remove_slug($permalink, $post, $leavename) {
        global $wp_post_types;

        foreach ($wp_post_types as $type=>$custom_post) {
            if ($custom_post->_builtin == false && $type == "team_member") {
                $custom_post->rewrite['slug'] = trim($custom_post->rewrite['slug'], '/');
                $permalink = str_replace(get_bloginfo('url') . '/' . $custom_post->rewrite['slug'] . '/', get_bloginfo('url') . "/", $permalink);
            }
        }
        return $permalink;
    }
    
    public function rewrite_rules($flash = false) {
        global $wp_post_types, $wpdb;
        foreach ($wp_post_types as $type=>$custom_post) {
            if ($custom_post->_builtin == false && $type == "team_member") {
                $querystr = "SELECT {$wpdb->posts}.post_name
                                FROM {$wpdb->posts}
                                WHERE {$wpdb->posts}.post_status = 'publish'
                                        AND {$wpdb->posts}.post_type = '{$type}'
                                        AND {$wpdb->posts}.post_date < NOW()";
                $posts = $wpdb->get_results($querystr, OBJECT);
                foreach ($posts as $post) {
                    $regex = "{$post->post_name}\$";
                    add_rewrite_rule($regex, "index.php?{$custom_post->query_var}={$post->post_name}", 'top');            
                }
            }
        }
        if ($flash == true)
            flush_rewrite_rules(false);
    }
    
    
    private function add_rules_htaccess() {
        global $wp_post_types;
        $suffix = get_option('jafty_permalink_customtype_suffix');
        $write = array();
        $htaccess_filename = ABSPATH . '/.htaccess';
        if(is_readable($htaccess_filename)){
            $htaccess = fopen($htaccess_filename, 'r');
            $content = fread($htaccess, filesize($htaccess_filename));
            foreach ($wp_post_types as $type=>$custom_post) {
                $rewrite_rule = (!empty($suffix))
                            ? "RewriteRule ^{$custom_post->query_var}/(.+)/\$ /\$1\.{$suffix} [R=301,l]"
                            : "RewriteRule ^{$custom_post->query_var}/(.+)/\$ /\$1 [R=301,L]";
                if (strpos($content, $rewrite_rule) == false && $custom_post->_builtin == false)
                    $write[] = $rewrite_rule;
            }
            fclose($htaccess);
        }else{
            add_action('admin_notices', array(&$this, 'compatibility_notice'));
            return;
        }
        
        if (!empty($write) && is_writable($htaccess_filename)) {
            $new_rules = '# BEGIN ' . $this->htaccess_tag . PHP_EOL;
            $new_rules .= str_replace('$', '\\$', implode(PHP_EOL, $write)) . PHP_EOL;
            $new_rules .= '# END ' . $this->htaccess_tag;
            if (strpos($content, "# BEGIN {$this->htaccess_tag}") === false) {
                file_put_contents($htaccess_filename, $new_rules . PHP_EOL . PHP_EOL . $content);
            }
            else {
                $pattern = "/# BEGIN {$this->htaccess_tag}.*?# END {$this->htaccess_tag}/ims";
                $content = preg_replace($pattern, $new_rules, $content);
                file_put_contents($htaccess_filename, $content);
            }
        }else if(!is_writable($htaccess_filename))
            add_action('admin_notices', array(&$this, 'compatibility_notice'));
    }//end add_rules_htaccess function

    
    public function compatibility_notice() {
        global $wp_post_types;
        $rules = '';
        foreach ($wp_post_types as $type=>$custom_post) {
            if ($custom_post->_builtin == false && $type == "team_member") {
                $slug = str_replace('/', '', $custom_post->rewrite['slug']);
                $rules .= 'RewriteRule ^' . $slug . '/(.+)$ /$1 [R=301,L]<br />';
            }
        }
        
        echo '<div class="error fade" style="background-color:red;"><p><strong>Remove Slug Custom post type error!</strong><br />.htaccess is not writable, please add following lines to complete your installation: <br />'.$rules.'</p></div>';
    }
}//End JAFTY_CPT_SlugKiller class to remove slugs from team_member CPT

//actions and hooks for above class to remove slug from team_member CPT:
add_action('init', array('JAFTY_CPT_SlugKiller', 'init'), 99);
//the following two lines make it so you don't have to manually go to settings/permalinks and re-save settings for links to work:
register_activation_hook( __FILE__, array('JAFTY_CPT_SlugKiller', 'flush_rewrite_rules') );
register_deactivation_hook( __FILE__, array('JAFTY_CPT_SlugKiller', 'flush_rewrite_rules') );
//END CODE TO ENABLE NO SLUG FOR team_member CPT

?>

The above code can be added to a plugin file or your themes functions.php.

NOTE: although the above code hasn't been thoroughly tested on non-nginx servers, it should work if you un-comment the two lines that call the add_rules_htaccess function. Simply do a search for "add_rules_htaccess" and remove the "//" before it in two occurrences and this code should work on any server as long as your .htaccess file is writable.

One thought on “How to Remove Slugs for a Custom Post Type in WordPress

  1. Hi, thanks for the code - seems to be working a charm for a single CPT.

    Can you provide an example for the neatest way to allow this to match multiple CPT's without too much code repetition?

    Thanks
    Alex

Leave a Reply

Your email address will not be published. Required fields are marked *