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; ?>" />

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; ?>" />

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; ?>" />

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; ?>" />

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; ?>" />

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":
  • how about replacing just the labels?
    – Bainternet
    Commented Apr 24, 2013 at 7:26
  • Just the labels? I'm not sure exactly what you mean but I would prefer to change it thoroughly throughout.
    – Desi
    Commented Apr 24, 2013 at 7:29

6 Answers 6


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.

  • 1
    Ah, sorry should have mentioned. There are lots of posts in portfolio already.
    – Desi
    Commented Apr 24, 2013 at 7:37
  • i edited the answer. goodluck! Commented Apr 24, 2013 at 7:46
  • This is a fantastic plugin!
    – realph
    Commented Jan 7, 2014 at 17:47
  • 3
    this plugin is not working properly anymore. i tried it and it didn't change all 'old' post types. but the mysql solution (answer by Will) works fine.
    – honk31
    Commented Jan 5, 2017 at 9:09
  • 1
    Plugin has not been updated in two years.
    – rhand
    Commented Jan 17, 2017 at 8:29

You can do this directly with MySQL as well.

UPDATE `wp_posts`
    # 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 );
  • 1
    Thanks. This works. One minor thing: the last part of the second query in the loop should be "%post_type={$old_type}%", not "%post_type={$type}%".
    – Betty
    Commented Dec 21, 2019 at 3:15
  • Just converted the wpdb queries into plain sql queries for simplifing. --- UPDATE wp_posts SET post_type = REPLACE(post_type, old_type, type) WHERE post_type LIKE '%old_type%'; UPDATE wp_posts SET guid = REPLACE(guid, 'post_type=old_type', 'post_type=type') WHERE guid LIKE '%post_type=old_type%'; UPDATE wp_posts SET guid = REPLACE(guid, '/old_type/', '/type/') WHERE guid LIKE '%/old_type/%';
    – Ahmad Wael
    Commented Apr 3, 2023 at 18:46

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
  • Very smart idea. Recommended for someone who dont wanna touch DB
    – kanlukasz
    Commented Apr 23, 2020 at 16:32
  • Thanks for this...I also followed the conversation here to init a new custom post type: wordpress.stackexchange.com/a/252078/142039 -- was able to import all my data, sweet!
    – twk
    Commented Apr 13, 2021 at 6:43

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%';
  • very neat to list all this up. except the sql query in the end is not complete, since you forgot about the guid, but they are listed in Wills answer. plus i wouldn't go with WHERE 'post_type' LIKE '%old_post_type%', i would use WHERE 'post_type' = 'old_post_type', since your way could also cause some other post-types, to change..
    – honk31
    Commented Jan 5, 2017 at 9:27

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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