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  /   179

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__ ) ) )
	 * 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);
		 * finally, redirect to the edit post screen for the new draft
		wp_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );
	} 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

Subscribe to this weekly newsletter to receive the latest blog posts by email.I respect your privacy. Your email is safe with me.

Comments 179

← Older
  • Great code here, Could you possibly help me add the ability to duplicate all the posts children as well? willing to pay.

  • Hi,

    The Clone link doesn’t appear for Marketpress products. Are you able to fix?



  • Hi Misha, i read this

    There are only two filters:

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

    How do i have for both please. thanks.

  • Hi,
    good solution! I have two wordpress, in the same server. WordPress use two database with same data. How I can setup the destination database before wp_insert_post? If it’s possible, I can use this plugin to duplicate post between two wordpress (i.e from staging to prod env). Thanks!

  • Kshitij Divakar November 6, 2017 at 22:08

    Great ! Up to the Mark.

  • Christopher Simmons November 8, 2017 at 03:15

    Thanks for this; was looking all over the place for code to do this simply. Will be trying this out tomorrow. :-)

  • Thanks so much for this! Really appreciate you sharing your solutions with the WordPress community at large. Saved me from downloading yet another plugin.

    • MishaAuthor December 7, 2017 at 23:18

      I’m happy to help 🙃
      Always welcome!

      • Also, thanks for being so thorough with your comments! Incredibly useful as I’m trying to learn more PHP and WordPress programming. Went through your code line-by-line with PHP docs and codex open right next to it. Learned more just reading through this than I have anywhere else.

  • I have website A and B. website A is current website. Website B is a new design of the website A.
    its a news portal. we publish 150 articles daily.

    we are now live for website B. posts from website A was migrated at 3 days ago midnight.
    now we need to update the posts from Website A to Website b for the past 3 days of posts.

    how can we do this without a wp plugin.

    Website A > WP 4.9.
    permalink strucure :

    Website B > 4.8.2
    permalink strucure :

  • Hi,
    The link for custom post types doesn’t works.
    What is the date of this comment ?

    • MishaAuthor January 5, 2018 at 13:28

      Hey Martin,

      Custom post types were tested many many times 🙃
      I’m still not sure where was the problem for those guys who can not make it work for CPT.

Leave your question or feedback

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