How to Duplicate Posts and Pages Without Plugins

It is very useful when you work with a lot of similar posts. So, when you copy a post, you don’t have to enter all metadata, taxonomies etc. each time manually.

#noplugins, #wp-admin  /  June 17, 2013  /   151

Duplicating posts is a very useful functionality when you work with a lot of similar posts (for example products in online shop). Especially if the posts have the same custom fields values, but the different post title and content.

It means that you do not have to re-enter custom fields, post tags and categories each time.

This is the example:

Duplicate post link in post row actions.

When you click the «Duplicate» link, the post will be cloned, but it won't be published, it will be saved as a draft and you will be redirected to the post edit admin page.

This is enough easy to do, so, look at the following code, insert it into your theme functions.php file (or into the another file if you know what to do).

/*
 * Function creates post duplicate as a draft and redirects then to the edit post screen
 */
function rd_duplicate_post_as_draft(){
	global $wpdb;
	if (! ( isset( $_GET['post']) || isset( $_POST['post'])  || ( isset($_REQUEST['action']) && 'rd_duplicate_post_as_draft' == $_REQUEST['action'] ) ) ) {
		wp_die('No post to duplicate has been supplied!');
	}
 
	/*
	 * Nonce verification
	 */
	if ( !isset( $_GET['duplicate_nonce'] ) || !wp_verify_nonce( $_GET['duplicate_nonce'], basename( __FILE__ ) ) )
		return;
 
	/*
	 * get the original post id
	 */
	$post_id = (isset($_GET['post']) ? absint( $_GET['post'] ) : absint( $_POST['post'] ) );
	/*
	 * and all the original post data then
	 */
	$post = get_post( $post_id );
 
	/*
	 * if you don't want current user to be the new post author,
	 * then change next couple of lines to this: $new_post_author = $post->post_author;
	 */
	$current_user = wp_get_current_user();
	$new_post_author = $current_user->ID;
 
	/*
	 * if post data exists, create the post duplicate
	 */
	if (isset( $post ) && $post != null) {
 
		/*
		 * new post data array
		 */
		$args = array(
			'comment_status' => $post->comment_status,
			'ping_status'    => $post->ping_status,
			'post_author'    => $new_post_author,
			'post_content'   => $post->post_content,
			'post_excerpt'   => $post->post_excerpt,
			'post_name'      => $post->post_name,
			'post_parent'    => $post->post_parent,
			'post_password'  => $post->post_password,
			'post_status'    => 'draft',
			'post_title'     => $post->post_title,
			'post_type'      => $post->post_type,
			'to_ping'        => $post->to_ping,
			'menu_order'     => $post->menu_order
		);
 
		/*
		 * insert the post by wp_insert_post() function
		 */
		$new_post_id = wp_insert_post( $args );
 
		/*
		 * get all current post terms ad set them to the new post draft
		 */
		$taxonomies = get_object_taxonomies($post->post_type); // returns array of taxonomy names for post type, ex array("category", "post_tag");
		foreach ($taxonomies as $taxonomy) {
			$post_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'slugs'));
			wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false);
		}
 
		/*
		 * duplicate all post meta just in two SQL queries
		 */
		$post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id");
		if (count($post_meta_infos)!=0) {
			$sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) ";
			foreach ($post_meta_infos as $meta_info) {
				$meta_key = $meta_info->meta_key;
				if( $meta_key == '_wp_old_slug' ) continue;
				$meta_value = addslashes($meta_info->meta_value);
				$sql_query_sel[]= "SELECT $new_post_id, '$meta_key', '$meta_value'";
			}
			$sql_query.= implode(" UNION ALL ", $sql_query_sel);
			$wpdb->query($sql_query);
		}
 
 
		/*
		 * finally, redirect to the edit post screen for the new draft
		 */
		wp_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );
		exit;
	} else {
		wp_die('Post creation failed, could not find original post: ' . $post_id);
	}
}
add_action( 'admin_action_rd_duplicate_post_as_draft', 'rd_duplicate_post_as_draft' );
 
/*
 * Add the duplicate link to action list for post_row_actions
 */
function rd_duplicate_post_link( $actions, $post ) {
	if (current_user_can('edit_posts')) {
		$actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=rd_duplicate_post_as_draft&post=' . $post->ID, basename(__FILE__), 'duplicate_nonce' ) . '" title="Duplicate this item" rel="permalink">Duplicate</a>';
	}
	return $actions;
}
 
add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );

But what if this code works only for posts, not for pages or any registered post types? Do not worry, all we need to do is to change the last filter to this:

add_filter('page_row_actions', 'rd_duplicate_post_link', 10, 2);

For custom post types view this example in comments.
You can use several filters at the same time, of course.

Only the best of WordPress

once a week, no spam

Comments 151

  • Thanks a lot. So simple to add to functions.php and works great on WP 3.6.1.
    You saved me a lot of time. Thanks again.
    Christophe

  • Hi,
    thanks for this great plugin.
    Just what I was lookin’ for. Works like a charm :-)

    Ralf.

  • Ready to add the function to my Event plugin. Going to use it for setting up repeating events. I assume I use the $arg and $taxonomies from my custom post type rather than the ones you have listed.

  • Thank you so much! amazing!I got it to work the first time.

  • Hi,

    this code works smooth and so easy to use :)

    I just have one thing.

    It I duplicate a post which includes a gallery, than the items not visible in the copy at “Create Gallery > Media Library > Uploaded to this post”. It shows “No items found. Drop files anywhere to upload”.

    Of course, it shows the items in general, just not listed in the dropdown filter “Uploaded to this post”.

    It’s possible to “fix” it?

    Cheers
    Jingz :)

  • Leo CavalliniDecember 8, 2013 at 02:12

    Hey pal, thanks a lot… I was ready to install a plugin but am one of those who prefer the less amount of plugins possible.

    I’m gonna repeat the jingz question. Aren’t images related to the posts by slugs into the db? Can’t I relate the same image files to the duplicate post with different slugs? I mean… WP will automatically create new slugs when I try to insert the same values to the table, no?

    How do the featured image duplication works? Isn’t it what we are expecting?

    I’m using it in a bilingual blog that shares all the same stuff, custom fields, tags and category. Only title, slug and content is different from one sibling post to another. So doing this image linkage automatically would be beautiful.

    One more question, do I get the relation between both posts on-the-fly, in a way I can print the duplicated post and tell who was the original one?

    Congrats for your work, it saved me immensely.

    • Hi,
      This plugin copy the featured thumbnails but don’t save the image relations to the post.
      It don’t duplicate the images as well.
      Yes, you can save the original post ID as new post custom field value.

  • This is a great function. I was wondering is it possible to remove the default category. Everytime I do a duplicate, it populates the default category too.

  • Hi

    Misha, the post images are not duplicating when i use your code.Can you give a solution for duplicating the post gallery

    • Hi!
      do you mean images, attached to post? Then you should also include the loop with duplicating all post children (post type attachment).

      • Am I missing something? Where is the loop you speak of that I need to include so that I may copy the image attached to the post?

  • This works only for posts and not for pages even if we change the last line of the above code.

    Please check or let us know the exact code to duplicate pages.

  • Dennis OuelletteFebruary 18, 2014 at 14:02

    Worked the first time. Awesome function. Thanks!

  • Brilliant! Many many thanks for sharing your superb code, Misha (including your very helpful comments). It does exactly what I wanted and will save lots of time in creating pages.

  • Kevin BrandsMarch 11, 2014 at 12:03

    Hello Misha,

    I’ve succesfully used your code on a custom post type I’ve created. But suddenly, it stopped working. I’ve been thinking why it stopped working, and I don’t have a clue.

    I’ve tried disabling/deleting plugins. No luck.

    Then I tried enabling/disabling it on other custom post types I’ve created in the same theme. The strange thing is: when I enable it for the standard ‘post_row_actions’, it shows on one of the custom post types. But when I enable it specifically for this custom post type like so ‘vragen_row_actions’, it doesn’t work.

    As for the other custom post types: it doesn’t work anymore at all.

    Do you know what could be going on?

  • Misha

    Great snippet of code! Thanks so much. I’m having the same problem as Kevin above… using ‘event_row_actions’ doesn’t work, but if I use the generic ‘post_row_actions’ it was, albeit for all posts, which is what I’m trying to avoid.

    That said, I can get around that by check post type in the function itself by add ‘($post->post_type == ‘event’)’ to the if statement.

    —-

    My second question is about the redirect… Since I’m using this for an events plugin, I’d rather not redirect the user to the post edit screen. I would prefer that the duplicate post is created, saved as a draft, and the user remains on the list view page. They could then click quick edit to simply change the date of the event since thats all that changes.

    I tried just commenting out the wp_redirect() line but this still tried to redirect the user and produces a blank screen.

    Any thoughts on how to accomplish this?

    Any help is greatly appreciated.

    • MishaAuthorMarch 13, 2014 at 08:03

      Hi Bruce,
      the answer to you first question is here,
      as for redirect — just change this line of the code

      wp_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );

      to

      wp_redirect( admin_url( 'edit.php' ) ); // or edit.php?post_type={here your custom post type name}
  • tslil aloniMarch 16, 2014 at 12:03

    hey!

    first of all, thanks!

    second – can you write the filter for duplicating portfolio items?

    thanks alot!!!
    tslil

  • SWEET bit of code! – Saved me a bunch of time, a bunch of times!

    Cheers x

  • Misha, this is such a great solution. Saves me tons of time when I’m creating dummy content and it already includes taxonomy and meta data. It’s perfect! This will be a huge time saver for admins/clients too! Thank you for sharing this!

  • Thanks for a simple, effective method to duplicate posts! most appreciated, worked like a charm.

  • If I have portfolio content type, how would I use this code to do something similar. Is there a way to modify this easily?

  • Paal JoachimJanuary 16, 2015 at 15:01

    I added your duplicate post code to my web page:
    http://easywebdesigntutorials.com/an-easy-way-to-duplicate-posts-or-pages/

    Have a great day!

    Thank you for the snippet!

    Have you thought about creating a WordPress trac ticket https://core.trac.wordpress.org/ suggesting that duplicate post is added to Core?

  • Fab! Works perfectly! :)

    Thanks so much!!

  • I hate to be the bearer of bad news, but this is WIDE open to hostile SQL injection and you fail to sanitize any of the GET/REQUEST variables with absint() and such like you should. I could drop your tables/database in a couple of characters on any site that uses this.

    Outside of that it looks rather helpful so thank you for posting :)

  • For some reason, the meta data never would copy for me, so I replaced your “duplicate all post meta” section with this, and it seemed to work like a charm:

    $data = get_post_custom($post_id);
    foreach ( $data as $key => $values) {
    	foreach ($values as $value) {
    		add_post_meta( $new_post_id, $key, $value );
    	}
    }
  • Hi.. I try this code in my local host.. and try to write with duplicate.. but the Link was numbered and still publish..

    any ideas for it or it’s not work in local host ?
    thanks

  • Is there a way to just duplicate the featured image? Instead of copying all meta information, the only meta I want is the featured image. How would I go about that?

    Thanks

    • MishaAuthorAugust 14, 2015 at 05:08

      I think it will be enough to copy the meta with _thumbnail_id key.

      • So how would I rewrite the following code to just duplicate the

         _thumbnail_id

        key? Sorry I’m not really a database guy so I’m not too familiar with how to handle queries.

        /*
         * duplicate all post meta
         */
        $post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id");
        if (count($post_meta_infos)!=0) {
          $sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) ";
          foreach ($post_meta_infos as $meta_info) {
            $meta_key = $meta_info->meta_key;
            $meta_value = addslashes($meta_info->meta_value);
            $sql_query_sel[]= "SELECT $new_post_id, '$meta_key', '$meta_value'";
          }
          $sql_query.= implode(" UNION ALL ", $sql_query_sel);
          $wpdb->query($sql_query);
        }

        Thanks

  • I have two custom post types, “Books” and “Author”. I want to get the plugin to duplicate from one post type to the other. So in “Books” I have a post called “Moby Dick” instead of “Duplicate Post” I have “Author details”, when I click “Author details”, it will duplicate the “Moby Dick” post to my other post type “Author”. Under the “Author” post type I will have a new post called “Moby Dick – Author”.

    Is that possible? Thanks in advance.

    • Yes, it is possible, please contact me via email.

      • Hi, can you post the solution, i want to change the post type whil duplicating.

        Thanks in advance.

        • MishaAuthorMarch 28, 2017 at 12:03

          Hi Jakob,

          just set the preferable post type on line 51 :)

          • Hi Misha,

            thx for the quick reply, but it did not seem to work for me.
            $args = array(
            ‘comment_status’ => $post->comment_status,
            ‘ping_status’ => $post->ping_status,
            ‘post_author’ => $new_post_author,
            ‘post_content’ => $post->post_content,
            ‘post_excerpt’ => $post->post_excerpt,
            ‘post_name’ => $post->post_name,
            ‘post_parent’ => $post->post_parent,
            ‘post_password’ => $post->post_password,
            ‘post_status’ => ‘publish’,
            ‘post_title’ => $post->post_title,
            ‘post_type’ => ‘deal’,
            ‘to_ping’ => $post->to_ping,
            ‘menu_order’ => $post->menu_order
            );
            Does still create a post_type product.

          • MishaAuthorMarch 29, 2017 at 11:03

            Hi Jakob,

            I made tests with converting post to page type and everything works for me.

  • The code duplicates posts and custom post types well but I am unable get an metapostdata to copy across?

    Thanks for your help

    • Hi,
      please, try this.

      • Thanks Misha

        I tried Corbin’s code and it worked on a standard post – I added an excerpt and that was duplicated.

        I need a bit more help if you guys have time. The theme I am using has what seems like the post type “Event” and this has extra meta fields, when I look at it in the database the post_type field is “event” – however, the duplicate link appears when this code is added:

        add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );

        and disappears if I change this to:

        add_filter( 'event_row_actions', 'rd_duplicate_post_link', 10, 2 );

        So I am not convinced that Event is it’s own custome post type? I’m getting confused.

        Thanks for your expertise.

  • p.s. I tried both

    add_filter( 'event_row_actions', 'rd_duplicate_post_link', 9999, 2 );

    and

    add_filter( 'post_row_actions', 'rd_duplicate_post_link', 9999, 2 );
    • There are only two filters:

      • page_row_actions — for page type,
      • post_row_actions — for posts and another custom post types.

      Ok, so I recommend you to make the following changes:

      function rd_duplicate_post_link( $actions, $post ) {
      	if ($post->post_type=='event' && current_user_can('edit_posts')) {
      		$actions['duplicate'] = '<a href="admin.php?action=rd_duplicate_post_as_draft&amp;post=' . $post->ID . '" title="Duplicate this item" rel="permalink">Duplicate</a>';
      	}
      	return $actions;
      }
       
      add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );
      • Hi Misha
        I tried this function for my ‘Events’ but is not working to me, is not even getting inside the function, I added a print_r(Hello) but is only working on the posts listing?

        function rd_duplicate_post_link( $actions, $post ) {
          print_r("Hello world!");
        	if ($post->post_type=='events' && current_user_can('edit_posts')) {
        		$actions['duplicate'] = '<a href="admin.php?action=rd_duplicate_post_as_draft&amp;post=' . $post->ID . '" title="Duplicate this item" rel="permalink">Duplicate</a>';
        	}
        	return $actions;
        }
         
        add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );

        any help would it be appreciated.
        Thank you!
        Marcos

        • MishaAuthorMay 21, 2016 at 07:05

          Hi Marcos,
          are you sure, that your post type name is “events” not “event”? Usually plural forms are not used for post type names.

          • Hey, first of all, thanks for this code Misha. I have the same problem. When i and the $post->post_type==’projectes’ (my custom post type) it stops working. Any ideas?

          • MishaAuthorApril 1, 2017 at 15:04

            Hi Juan,

            for me it just works. Yes, for custom post types. I tried to broke it but with no luck. Please contact me by email and I will help you.

  • Hello,

    This works great, however what if I am using a polylang plugin? I have a multilanguage site and when clicking duplicate it only duplicates the current language and not both languages. Any idea how to duplicate both language posts at the same time. They are somehow linked to each other but when clicking duplicate the post is duplicated however the link stays the same pointing the old post translation.

    Thanks!

  • Hi MIsha, I setup a child theme, and also have a functions.php file in the child folder.

    When I paste the code into the child folder functions.php, it does NOT work; when I paste it in the parent functions.php, it does work.

    Is there a way to make it work in the child folder that I’m missing? Thanks!

    • MishaAuthorJanuary 9, 2016 at 08:01

      Hi Troy,

      I made some tests — the code works good with the child themes. I’m not sure where your problem is, please try to change filter priority here.

      add_filter( 'post_row_actions', 'rd_duplicate_post_link', 9999, 2 ); // from 10 to 9999 for example
      • Genius Misha – that change from 10 to 9999 worked a treat for my child theme! Thanks for the time and effort. Great piece of code generously given :-)

  • Captain BaconFebruary 4, 2016 at 13:02

    Thank you Misha, You saved my life!

  • Thanks :)

  • Firstly, great code. It works out of the box.

    What if I want a duplicate link/button on the page/post itself? Is that possible?

    • MishaAuthorJune 9, 2016 at 09:06

      Hello,
      do you mean post/page edit wp-admin/post.php?post=2202&action=edit pages?

      • Hi Misha!

        First, thank you! This is saving me a ton of time and is one less plugin to install.

        Second, I think I am trying to do something similar to JJ Human’s question. I have built a site where all of the editing of posts is done through ACF forms on the front end. I am limiting access to the wp-admin pages to a few users.

        I already changed wp_redirect() to redirect to the permalink of the new post. This isn’t working. Right now it redirects to a blank version of the original post.

        Is there anyway to execute this code to redirect to the front end version of the website?

        Thanks so much!

        Jess

        • UPDATE: I figured out how to publish the post and redirect it from the admin link. Turns out I was using an invalid post status.

          Now I just need to create a template tag that will create a link to do the same thing on my single.php page.

  • Thanks! I refuse to install any more plugins for these small things:)
    Works perfectly:)

  • Hi. I just have tried it and it looks great.
    I simply want to thank you, Misha and other contributors, for sharing the code and especially for the willingness to help and collaborate.

    I also have few questions, possibly naive since I am not a deep technical guy in php and wordpress:

    1. If I append your code in my php (I have other code already there) it won’t execute. If I prepend it (as the first block of code in the file) then it works fine. I assume there would be no restriction on where I place the code inside the file, right?
    (I ask that because I then suspect I have some errors in my pre-existing code :-|

    2. Though I could not find it explicitly stated, I assume I can have both filter working at the same time:

    //
    // both filter working
    //
    add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );
    add_filter('page_row_actions', 'rd_duplicate_post_link', 10, 2);

    It seems to work for me, so that I can have the “duplicate” option both on posts and pages at the same time.
    Or was it intended to have just one filter active at a time?
    Thanks a lot!

    • MishaAuthorJuly 27, 2016 at 05:07

      Hi Cristiano,
      yes, there are no restrictions of where you placing the code, maybe you place it incorrectly after the closing ?> tag.

      Yes, you can use both filters at the same time :)

  • Hi! I just added this to my blog and it works fantastic!
    One question though, the page views for the posts are also duplicated so it didn’t restart at 0 (and I won’t be able to get an estimate of how many people have viewed my new post).
    Is there a way to reset the page view of the new duplicated post to 0?
    Thank you for making my life easier :)

  • Excellent!

  • I’ve been using a post duplicating plugin for a while, and I think it has royally screwed my SEO. The duplicator also duplicates the meta data, so it won’t let me push the cloned posts out (as a new post) to social media. Is this also something I can control as a code level, as easily as the above?
    Thanks for your thoughts.

    • MishaAuthorAugust 15, 2016 at 10:08

      Hi Kelley,
      I’m not sure that I understand your question.

      So, do you want to prevent the duplication of the meta data?

      • Sorry for not being clearer, Misha. When I use a post duplicating plugin, I can’t automatically push the cloned post out to social media via Jetpack. Jetpack reads the meta data of the original post; therefore, it won’t recognize the cloned post as an original, to be pushed out to social media upon publishing. If I duplicate a post by using this code modification, will I be able to push cloned posts out as new ones using Jetpack (or another publicizing plugin)?
        Thank you!

        • MishaAuthorAugust 16, 2016 at 09:08

          Ok, now I understand your problem.

          Some thoughts about it:

          1. Duplicating posts won’t increase your SEO, it may bring you the opposite effect I think.

          2. On the official Jetpack website is said that Publicize never will be triggered for the cloned posts — I don’t know how it checks it — you can try to clone post without meta data for a first time and look at the effect. To turn off copying meta data, remove from the code lines 67 – 77. If after that the post will be pushed to social media, then everything is left to do for you – just to find out what meta data keys to clone.

  • Thanks this is just what I needed

  • Very nice, huge help. Thank you!

  • doesnt work in WP 4.6

  • Awesome!
    Thank you so much.

  • Thanks for this nice script.

    you should use a prepare state for the SQL statment, but adding “(int)” to this should make it a bit saver to:

    $post_id = (int)(isset($_GET['post']) ? $_GET['post'] : $_POST['post']);

    Just to make sure the page is not locked when someone is using it when you make a copy:

    if ($meta_key != '_edit_lock') $sql_query_sel[]= "SELECT $new_post_id, '$meta_key', '$meta_value'";
  • Hi. I’m trying to modify this to allow a subscriber to republish a post on the front end (for an image and video social site so we don’t care as much about duplicate content). These are the changes I made but it’s not creating the post. Thanks for any insight you may have.

    'post_status'    => 'publish'

    added bottom two lines here:

    /* Get original post author and id info */
    $orig_author = get_post_meta($post_id, 'orig_author', true);
    $orig_post_id = get_post_meta($post_id, 'orig_post_id', true);
    if ( $orig_author == '') {
          $orig_author = $post->post_author;
           $orig_post_id = $post->ID;
       }
       /*
     * duplicate all post meta just in two SQL queries
    */
    $post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id");
    $post_meta_infos['orig_author'] = $orig_author;
    $post_meta_infos['orig_post_id'] = $orig_post_id;

    and called it using this function instead of post_row_actions hook…

    function man_renail_button( $post_id ) {
    	if(current_user_can('copy_posts')) {
    		$link = '<a class="simplebtn red" href="admin.php?action=rd_duplicate_post_as_draft&amp;post=' . $post_id . '" title="Renail" rel="permalink">><i class="fa fa-gavel"></i> Renail This</a>';
    		echo $link;
    	}
    }

    Then redirected to post with

    wp_redirect( get_permalink( $new_post_id ) );

    It did not create the post however and went to a 404 page. Tested with Admin role to ensure roles were not an issue but still didn’t work. Any ideas or something I’m missing?

    • Hi Tim,
      Does it create a draft or it it doesn’t create anything?

    • It doesn’t create anything.

      • First of all, if you insert code like this in website front-end, it won’t provide access to admin.php

        $link = '<a class="simplebtn red" href="admin.php?action=rd_duplicate_post_as_draft&amp;post=' . $post_id . '" title="Renail" rel="permalink">><i class="fa fa-gavel"></i> Renail This</a>';

        Try to change it to:

        $link = '<a class="simplebtn red" href="' . site_url() . '/wp-admin/admin.php?action=rd_duplicate_post_as_draft&amp;post=' . $post_id . '" title="Renail" rel="permalink">><i class="fa fa-gavel"></i> Renail This</a>';
        • That worked but I had to open up my backend. I might have to make this happen with an ajax call to avoid that.

          • Ok, AJAX is the other story, please contact me by email, so I could help you with the code.

          • Great work on this function. Is there a way to make this work without allowing the user access to the WordPress dashboard? I have a site where users shouldn’t be allowed into the backend.

          • Ok I will write another post later with the instructions how to duplicate posts outside the dashboard.

    • Hi Tim,

      Did you get it working? I have the same need. New to WordPress and php. Help is greatly appreciated. Thanks!

      • Hi, John… here is my code that I used. It’s called by a button click that is show on the front end by the function call also included (although there is some custom CSS here you will probably want to tweak). It uses a custom AJAX call to run the duplicate post function. It also saves the original post author in postmetas. Hope this helps. Also @Misha feel free to use this code for a separate post for duplicating posts outside the dashboard.

        PHP file

        <?php 
        /**
         * Plugin Name: Custom Repost Plugin
         * Plugin URI: http://pluggedinconcepts.com
         * Description: This is a plugin that allows us to renail existing posts.
         * Version: 1.0.0
         * Author: Tim Wolf
         * Author URI: http://pluggedinconcepts.com
         */
         
        add_action( 'wp_enqueue_scripts', 'mant_custom_enqueue_scripts' );
        function mant_custom_enqueue_scripts() {
        	if(is_user_logged_in()){
        		wp_enqueue_script( 'renail', plugins_url( '/js/renail.js', __FILE__ ), array('jquery'), '1.0', true );
         
        		wp_localize_script( 'renail', 'renailme', array(
        			'ajax_url' => admin_url( 'admin-ajax.php' )
        		));
        	}
        }
         
        /*
         * Function creates post duplicate as a draft and redirects then to the edit post screen
         */
        function man_duplicate_post(){
        	if(is_user_logged_in()){	
        		global $wpdb;
        		if (! ( isset( $_GET['post']) || isset( $_POST['post'])  || ( isset($_REQUEST['action']) && 'man_duplicate_post' == $_REQUEST['action'] ) ) ) {
        			wp_die('No post to duplicate has been supplied!');
        		}
         
        		/*
        		 * get the original post id
        		 */
        		$post_id = (isset($_GET['post']) ? absint( $_GET['post'] ) : absint( $_POST['post'] ) );
        		/*
        		 * and all the original post data then
        		 */
        		$post = get_post( $post_id );
         
        		/*
        		 * if you don't want current user to be the new post author,
        		 * then change next couple of lines to this: $new_post_author = $post->post_author;
        		 */
        		$current_user = wp_get_current_user();
        		$new_post_author = $current_user->ID;
         
        		/*
        		 * if post data exists, create the post duplicate
        		 */
        		if (isset( $post ) && $post != null) {
         
        			/*
        			 * new post data array
        			 */
        			$args = array(
        				'comment_status' => $post->comment_status,
        				'ping_status'    => $post->ping_status,
        				'post_author'    => $new_post_author,
        				'post_content'   => $post->post_content,
        				'post_excerpt'   => $post->post_excerpt,
        				'post_name'      => $post->post_name,
        				'post_parent'    => $post->post_parent,
        				'post_password'  => $post->post_password,
        				'post_status'    => 'publish',
        				'post_title'     => $post->post_title,
        				'post_type'      => $post->post_type,
        				'to_ping'        => $post->to_ping,
        				'menu_order'     => $post->menu_order
        			);
         
        			/*
        			 * insert the post by wp_insert_post() function
        			 */
        			$new_post_id = wp_insert_post( $args );
         
        			/*
        			 * get all current post terms ad set them to the new post draft
        			 */
        			$taxonomies = get_object_taxonomies($post->post_type); // returns array of taxonomy names for post type, ex array("category", "post_tag");
        			foreach ($taxonomies as $taxonomy) {
        				$post_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'slugs'));
        				wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false);
        			}
         
        			/* Get original post author and id info */
        			$orig_author = get_post_meta($post_id, 'orig_author', true);
        			$orig_post_id = get_post_meta($post_id, 'orig_post_id', true);
        	        if ( $orig_author == '') {
        	            $orig_author = $post->post_author;
        	            $orig_post_id = $post->ID;
        	        }
        	        /*
        			 * duplicate all post meta just in two SQL queries
        			 */
        			$post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id");
        			$post_meta_infos['orig_author'] = $orig_author;
        			$post_meta_infos['orig_post_id'] = $orig_post_id; 
        			if (count($post_meta_infos)!=0) {
        				$sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) ";
        				foreach ($post_meta_infos as $meta_info) {
        					$meta_key = $meta_info->meta_key;
        					$meta_value = addslashes($meta_info->meta_value);
        					$sql_query_sel[]= "SELECT $new_post_id, '$meta_key', '$meta_value'";
        				}
        				$sql_query.= implode(" UNION ALL ", $sql_query_sel);
        				$wpdb->query($sql_query);
        			}
         
        			/*
        			 * finally, redirect to the new post page
        			 */
        			$results = array(
        				'error' => false,
        				'error_msg' => '0',
        				'redirect' => get_permalink( $new_post_id )
        			);
        			echo json_encode($results);
        			die;
        		} else {
        			$results = array(
        				'error' => true,
        				'error_msg' => 'Post creation failed, could not find original post: ' . $post_id,
        				'redirect' => get_permalink()
        			);
        			echo json_encode($results);
        			die;
        		}
        	} else {
        		$results = array(
        				'error' => true,
        				'error_msg' => 'You are not logged in.',
        				'redirect' => wp_login_url( get_permalink() )
        			);
        		echo json_encode($results);
        		die;
        	}
        }
        add_action( 'wp_ajax_no_priv_man_duplicate_post', 'man_duplicate_post' );
        add_action( 'wp_ajax_man_duplicate_post', 'man_duplicate_post' );
         
        /*
         * Add the duplicate button in the page template
         */
        function man_renail_button( $post_id ) {
        	if(is_single()) {
        		$link = '<a class="renail-btn simplebtn red" href="' . admin_url() . 'admin-ajax.php?action=man_duplicate_post&amp;post=' . $post_id . '" title="Renail" rel="permalink"><i class="fa fa-gavel"></i> Renail This</a>';
        		echo $link;
        	}
        }

        JS File for AJAX call

        jQuery( document ).on( 'click', '.renail-btn', function() {
        	var post_id = jQuery(this).data('id');
        	jQuery.ajax({
        		url : renailme.ajax_url,
        		type : 'post',
        		data : {
        			action : 'man_duplicate_post',
        			post : post_id
        		},
        		success : function( data ) {
        			if( data.error ) {
        				alert(data.error_msg);
        			}
        			window.location = data.redirect;
        		}
        	});
         
        	return false;
        });
  • Thank you so much for this post. Super helpful.

  • Oren Zur-ShavitSeptember 29, 2016 at 14:09

    So helpful, TNX!

  • Absolutely wonderful, simple and complete solution. Thank you for sharing.

  • Hello,

    What a nice script ;) I have successfully integrated it with every improvement of this comment thread. But I really want to expand it to fit my needs, and I require some help in order to achieve this.

    What I want to do is to create 2 new “duplicate link” in order to have a “Duplicate” link, a “Generate the news related article” link and finally a “Generate the forum article” link.

    Why have I these needs? Cause for each subject of my website, I have 2 related articles with the same meta, taxonomies etc… only the title and the content will change. If I publish a post named “Photoshop”, I’ll create just after this 2 others related posts “News – Photoshop” and “Forum – Photoshop”.

    My question now : how can I achieve this with a single function? I can actually make 3 different functions for the 3 “duplicate link” I want, but this will makes unnecessary request.

    These 2 additional duplicate links must change the title by adding a prefix (‘News -‘ and ‘Forum -‘), and erasing the content (kinda easy, just delete the line from the $args).

    If someone can help me to achieve this, I’ll really appreciate this.

  • Great post and still valid over three years later :) Thank you.

    Just a heads up if anyone else comes along here and have an issue where if you duplicate a post, edits it but keeps it in draft mode and then checks the original post in the frontend only to see that the content of the original post seems to have been updated as well.

    This seems has to do with the fact that

    post_name

    of the new post gets set to the same value as the original. I solved this by simply removing the line

    'post_name'  => $post->post_name,

    (40 in the example code at the time of writing this). This sets the post-name to a “slug version” of the title (which, even though it is set to the same as the original post, will be changed by WP when wp_insert_post is executed).

    • Thank you for your comment! :)

    • Bjorn: Thanks man. Was driving me insane why “drafts” seemed to affect the published post from which it was first duplicated from.

      Misha: Million thanks for the initial post. This really will help save Client headaches ( or at least mine when teaching them how to post on their new website ).

    • +1 on this! Thank you!

  • Misha-san, thanks so much! I just tried and worked like a champ. Saved my time!!!!!

  • Thanks! This is great!

  • What if I want to duplicate a page like how Apple does for different regions?
    Example:
    – apple.com (US)
    – apple.com/sg (Singapore)
    – apple.com/ae (Middle East)

    Can it be done with some sort of a splash screen? When the visitor goes to website.com, there is a pop-up with option for their desired content, and it directs them there?

    Thanks in advance for those who will! :)

  • This easily worked for me in WordPress 4.6.1. Thanks for sharing this.

  • Thank you! It works great! Is possible to exclude one or two meta keys?

    • Hi Christopher,

      please sorry for such a delay in replying.

      You can easily exclude two meta keys by adding them into the SQL query on line 67.

      $post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id AND meta_key != 'key1' AND meta_key != 'key2'");
  • Hi

    What about if we want to have checkbox in edit post section with this function?

    Thanks!

    • Hi,
      could you please give me more info about how checkboxes should work?

      • Hi Misha thanks for your replay!

        First it can be simple metabox with just one checkbox “Would you like to duplicate this”
        if you check it will be duplicated only when it is published once.

        Could we change the code to copy to specific category and to leave all other taxonomies ?

        Regards!

        • Hi Nikola,

          I think yes you could, try to connect this code to save_post action hook.

          So in the function connected to save_post you need to check if checkbox value == “on”, and if yes – run the duplicate function.

  • Hi, thanks for the script, it works well, however I need to exclude a custom field which is providing a unique ID to each custom post.
    I have added an ‘if’ clause to exclude my specific field and it seems to work:

    if ($meta_key != 'my_excluded_custom_field') {
    	$sql_query_sel[]= "SELECT $new_post_id, '$meta_key', '$meta_value'";
    }

    Seems to work for me, but perhaps there is a better way?

  • THANK YOUUUUU

  • Awesome function.. Thanks!!

  • Jordan ChartersJanuary 17, 2017 at 12:01

    Is this still working on the latest version of WordPress 4.7.1?

  • Please, guide me further on in the article editing

  • Good job, thanks!

  • Of course, you MUST add a nonce to the link and the nonce verification on the action handler!

  • Thanks, that worked beautifully and took < 1 minute.

  • Shahzad AfzalApril 12, 2017 at 07:04

    Wow you are awesome…. Googled -> Copy Paste -> Voila :D

  • Shahzad AfzalApril 24, 2017 at 11:04

    Question, somebody can please help me, i want to remove the duplicated posts link with original post. For example I remove the old post the URL of the old post redirect to duplicated post.

    I hope you understand, it is something related to _wp_old_slug meta key. But I did not understand how to stop it.

    • MishaAuthorApril 25, 2017 at 07:04

      Hi,

      can you describe it in details? It is still not 100% clear for me.

    • MishaAuthorApril 25, 2017 at 15:04

      I’ve updated the code with this line:

      if( $meta_key == '_wp_old_slug' ) continue;

      Please try it now.

      • 1. And in case that we need to exclude multiple meta for some more meta filed ? :)

        2. And how we can change category?
        For example to duplicate always to same category

        3. How we can duplicate attached featured image?

        Regards

        • MishaAuthorApril 26, 2017 at 14:04

          1. Use || in your if condition.

          2. Replace the part of the code that duplicates terms with the only one function:

          wp_set_object_terms( $new_post_id, $category_id, 'category');

          and pass your category slug/ID there.

          3. I have no ready code for that, but if you find a solution, please, share the code in comments here :)

        • MishaAuthorMay 5, 2017 at 19:05

          :(

          try to play with wp_set_object_terms() and with if statements. Nothing difficult there.

  • I think he mean if you change title of post, than use duplicate function it will copy meta field with old slug.

    When you delete original post, wp will keep redirecting to duplicated post because of old slug meta field

    Regards

  • Thanks so much! Saved hips time :)
    BR

Leave your question or feedback

phpjsHTMLCSSSQLCode
Please, enter a comment
Please, enter a name
Incorrect email