Wordpress - How do I safely change the name of a custom post type?

You can do this directly with MySQL as well.

UPDATE `wp_posts`
SET 
    # Update the post_type column
    `post_type` = REPLACE(`post_type`,'name_of_old_post_type','name_of_new_post_type'),
    # Update the urls
    `guid` = REPLACE(`guid`,'name_of_old_post_type','name_of_new_post_type')
WHERE `post_type` = 'name_of_old_post_type'

Two things to note:

  1. You'll need to update any references to this post type in your code (say, templates, CMB2 definitions or taxonomy definitions).
  2. If you have stored any references to this post type within wp_postmeta within serialized arrays, you don't want to do a simple UPDATE/REPLACE because it'll blow them up! Well, unless both the new and old post type strings are the exact same length.

Extending Will's answer a bit further..., and especially if you are doing it from your plugin:

global $wpdb;
$old_post_types = array('old_type' => 'new_type');
foreach ($old_post_types as $old_type=>$type) {
    $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_type = REPLACE(post_type, %s, %s) 
                         WHERE post_type LIKE %s", $old_type, $type, $old_type ) );
    $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET guid = REPLACE(guid, %s, %s) 
                         WHERE guid LIKE %s", "post_type={$old_type}", "post_type={$type}", "%post_type={$old_type}%" ) );
    $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET guid = REPLACE(guid, %s, %s) 
                         WHERE guid LIKE %s", "/{$old_type}/", "/{$type}/", "%/{$old_type}/%" ) );
}

The change here is to not replace old type in the guid directly, but replace only if "post_type=old_type" or "/old_type/" is present. This avoids replacing valid slugs by mistake. (e.g. your custom post type is portfolio, and a page's slug too has portfolio in it)

Another alternative is to do something like this:

global $wpdb, $wp_rewrite;
foreach ($old_post_types as $old_type=>$type) {
    $q = 'numberposts=-1&post_status=any&post_type='.$old_type;
    $items = get_posts($q);
    foreach ($items as $item) {
        $update['ID'] = $item->ID;
        $update['post_type'] = $type;
        wp_update_post( $update );
    }
}
$wp_rewrite->flush_rules();

Use a WordPress Database Query but Don't Forget About Serialized Option Data

The method that worked for me was to do a search and replace within the WordPress database, but making sure to not screw up serialized option data in the process. The best way I've found is to use the safe search and replace database utility from interconnect/it. Never just do a SETpost_type= REPLACE(post_type,'old_post_type','new_post_type') type query without knowing what you are doing or serialized data will break since it keeps a checksum and won't be able to unserialize properly.

Read the Potential Issues section before blindly following this

Step 1 - Safely Update Your Database with New Name

  1. backup your database because the following changes have a very real potential to corrupt it.
  2. download and unzip the safe search and replace database utility from interconnect/it
  3. add the extracted directory to your webroot (it also works in subdirectories)
  4. browse to the directory, e.g: /mywebsite.com/path/to/utility/directory/
  5. follow directions. click 'dry-run' if you are paronoid to see the changes (there will be hundreds if you even have a few posts of the changed post type)
  6. click 'live run' to finalize the changes.
  7. remove the safe search directory from your wordpress directory since its a security issue

Step 2 - Reset Your Permalinks

If you are using permalinks, the updates to your database will screw up your redirects to your custom post types. There is an easy fix though, just go into WordPress settings/permalinks and note the current setting (mine was 'post name'). Then switch back to default, click 'save' , then back to the previous setting, then save again. You've just fixed your redirect issues.

Step 3 - Rename Your Theme's Custom Post Type Templates

If you're like me, and you created custom post type templates, you'll need to rename these or your custom posts will look screwed up. Just go into your theme and find any file that has your old post type name in its file name and rename the file using your new post name. For example, I had to change single-project-portfolio.php to single-before-after.php when I changed my post type from project-portfolio to before-after.

Step 5 - Update Any Code

Do a file search and replace for your old custom post type name in the the theme and plugins folder. For me, I had several custom shortcodes that relied on making a decision on whether I was using one of my custom post types.

Test Everything

Potential Issues (read before starting this procedure)

Syndication Issues

If your custom post types were syndicated, realize that your initial search and replace will also change the guids of your posts, which will force all subscribers to see the old posts as new ones. I didn't have to deal with this, but if you need to, then consider manually choosing the tables that the safesearch utility processes, then manually updating any non-serialized data using the following query:

SET `post_type` = REPLACE(`post_type`,'old_post_type','new_post_type')
WHERE `post_type` LIKE '%old_post_type%';