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

The question:

I made a custom post type called ‘portfolio’ but I want to change it to ‘projects’. What would be the exact steps I need to take in order to safely change the name and prevent the custom post type posts from disappearing in the dashboard?

Note: There are already posts in portfolio so I can’t just switch out portfolio with projects.

/* Register Portfolio Post Type */
add_action('init', 'create_portfolio');

function create_portfolio() {

    $labels = array(
        'name' => __('Portfolio', 'post type general name'),
        'singular_name' => __('Project', 'post type singular name'),
        'add_new' => __('Add New', 'portfolio item'),
        'add_new_item' => __('Add New Project'),
        'edit_item' => __('Edit Project'),
        'new_item' => __('New Project'),
        'view_item' => __('View Project'),
        'search_items' => __('Search Projects'),
        'not_found' =>  __('Nothing found'),
        'not_found_in_trash' => __('Nothing found in Trash'),
        'parent_item_colon' => ''
    );

    $args = array(
        'labels' => $labels,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'query_var' => true,
        'rewrite' => true,
        'capability_type' => 'post',
        'hierarchical' => false,
        'menu_position' => null,
        'supports' => array('title','editor','thumbnail')
      ); 

    register_post_type( 'portfolio' , $args );
}

/* Register Skills Taxonomy */
register_taxonomy("Skills", array("portfolio"), array("hierarchical" => true, "label" => "Skills", "singular_label" => "Skill", "rewrite" => true));

/* Add Fields */
add_action("admin_init", "add_portfolio_fields");

function add_portfolio_fields(){
    add_meta_box("website_url", "Website URL", "website_url", "portfolio", "side", "low");
    add_meta_box("view_more", "View More", "view_more", "portfolio", "side", "low");
    add_meta_box("screenshot_name", "Screenshot Name", "screenshot_name", "portfolio", "side", "low");
    add_meta_box("thumbnail_name", "Thumbnail Name", "thumbnail_name", "portfolio", "side", "low");
    add_meta_box("thumbnail_alt", "Thumbnail Alt", "thumbnail_alt", "portfolio", "side", "low");
}

function website_url(){
    global $post;
    $custom = get_post_custom($post->ID);
    $website_url = $custom["website_url"][0];
    ?>
    <label>Website URL:</label>
    <input size="50" name="website_url" value="<?php echo $website_url; ?>" />
    <?php
}

function view_more() {
    global $post;
    $custom = get_post_custom($post->ID);
    $view_more = $custom["view_more"][0];
    ?>
    <label>View More:</label>
    <input size="50" name="view_more" value="<?php echo $view_more; ?>" />
    <?php
}

function screenshot_name() {
    global $post;
    $custom = get_post_custom($post->ID);
    $screenshot_name = $custom["screenshot_name"][0];
    ?>
    <label>Screenshot Name:</label>
    <input name="screenshot_name" value="<?php echo $screenshot_name; ?>" />
    <?php
}

function thumbnail_name() {
    global $post;
    $custom = get_post_custom($post->ID);
    $thumbnail_name = $custom["thumbnail_name"][0];
    ?>
    <label>Thumbnail Name:</label>
    <input name="thumbnail_name" value="<?php echo $thumbnail_name; ?>" />
    <?php
}

function thumbnail_alt() {
    global $post;
    $custom = get_post_custom($post->ID);
    $thumbnail_alt = $custom["thumbnail_alt"][0];
    ?>
    <label>Thumbnail Alt:</label>
    <input name="thumbnail_alt" value="<?php echo $thumbnail_alt; ?>" />
    <?php
}

add_action('save_post', 'save_portfolio_details');

function save_portfolio_details(){
    global $post;

    update_post_meta($post->ID, "website_url", $_POST["website_url"]);
    update_post_meta($post->ID, "view_more", $_POST["view_more"]);
    update_post_meta($post->ID, "screenshot_name", $_POST["screenshot_name"]);
    update_post_meta($post->ID, "thumbnail_name", $_POST["thumbnail_name"]);
    update_post_meta($post->ID, "thumbnail_alt", $_POST["thumbnail_alt"]);
}

/* Custom Columns */
add_action("manage_posts_custom_column",  "portfolio_custom_columns");
add_filter("manage_edit-portfolio_columns", "portfolio_edit_columns");

function portfolio_edit_columns($columns){
    $columns = array(
        "cb" => "<input type="checkbox" />",
        "title" => "Project Title",
        "description" => "Description",
    );

    return $columns;
}

function portfolio_custom_columns($column){
    global $post;

    switch ($column) {
        case "description":
        the_excerpt();
        break;
    }
}

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

If you have no posts in your portfolio yet.

It would be really simple. Rename everything with “Portfolio” into “Projects”.
You will lose nothing and change the name.

Edit :

Try use this plugin http://wordpress.org/extend/plugins/ptypeconverter/ to export the current posts safely and import it into your new custom post type.

So the steps are :

1 Download and use the plugin : http://wordpress.org/extend/plugins/ptypeconverter/

2 Copy your custom post type “portfolio” file somewhere save. call it for example portfolio_post_typeBACKUP.php

3 Now you are sure when this method fails. you can recover it.

4 Change “portfolio” into “projects

5 Import the posts with the plugin and viola!

Hope this works.

Method 2

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.

Method 3

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();

Method 4

Here’s a really simple way :

  1. Run the WordPress Exporter (Tools > Export) – only export the post type you want to change the name of
  2. Open the generated .xml file and replace all mentions of the old post type name by the new name (in the “custom_post_type” meta as well as in the permalink field)
  3. Create your new post type with the same name as in the edited .xml (but still keep the old one in case it fails)
  4. Import the edited .xml file through the WordPress Importer (plugin available directly from Tools > Import)
  5. Check that the content is present in the new post type and then remove the old one

Method 5

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%';

Method 6

I don’t have the reputation to comment so I will put this here. Extending Will’s example. I changed the LIKEs to “=” and have them both point to WHERE post_type

UPDATE `wp_posts`
    SET `guid` = REPLACE(`guid`,'old_post_type','new_post_type')
    WHERE `post_type` = 'old_post_type'    

UPDATE `wp_posts`
    SET `post_type` = REPLACE(`post_type`,'old_post_type','new_post_type')
    WHERE `post_type` = 'old_post_type'

Also remember to go into the Admin > Settings > Permalinks and hit “Save Changes”. Otherwise your links will likely be broken.

You will also need to edit any ‘single-post-type’ template names.

This should be all you need to do.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment