Skip to content

Early-bird lifetime licence: $199 once, for the first 200 buyers only. See the offer

Duplicate a page or post in WordPress without a plugin

Add a Duplicate link to your posts and pages with one Snipfire snippet. Here is how it compares with Yoast Duplicate Post, feature by feature.

What Yoast Duplicate Post does

Yoast Duplicate Post copies posts of any type, with several ways to clone them and settings for what gets copied. The plugin on WordPress.org

What the snippet does the same

  • A link under each post and page in the admin lists makes a copy as a new draft and opens it in the editor.
  • The copy keeps the content, excerpt, categories, tags, custom fields and featured image.
  • Only people who can create and edit that kind of post see the link. It works for custom post types too.

What's different

  • The snippet has one Duplicate link in the admin lists. The plugin also has Clone and New Draft links, bulk cloning, and copy links on the edit screen and in the admin bar.
  • The plugin has Rewrite & Republish, which lets you edit a copy of a live page and merge it back later. The snippet doesn't.
  • The plugin has settings for which fields to copy, which roles can copy and which post types. The snippet always copies the same things and adds (copy) to the title.
  • The snippet leaves WooCommerce products out, because WooCommerce has its own Duplicate link.

When to keep the plugin Keep the plugin if you use Rewrite & Republish or need bulk cloning or role limits.

The snippet: Duplicate posts and pages

Adds “Duplicate” under each post and page in the admin lists. The copy is a draft with the same content, categories, tags, custom fields and featured image.

PHP
<?php

// WooCommerce products have their own "Duplicate", so they're left out.
$row_action = static function ( $actions, $post ) {
	$type = get_post_type_object( $post->post_type );
	if ( 'product' !== $post->post_type && 'trash' !== $post->post_status && $type && current_user_can( $type->cap->create_posts ) && current_user_can( 'edit_post', $post->ID ) ) {
		$url                  = wp_nonce_url( admin_url( 'admin-post.php?action=sf_duplicate_post&post=' . $post->ID ), 'sf_duplicate_post_' . $post->ID );
		$actions['duplicate'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), esc_html( 'Duplicate' ) );
	}
	return $actions;
};
add_filter( 'post_row_actions', $row_action, 10, 2 );
add_filter( 'page_row_actions', $row_action, 10, 2 );

add_action(
	'admin_post_sf_duplicate_post',
	static function () {
		$id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
		check_admin_referer( 'sf_duplicate_post_' . $id );
		$post = $id ? get_post( $id ) : null;
		$type = $post ? get_post_type_object( $post->post_type ) : null;
		if ( ! $post || ! $type || ! current_user_can( $type->cap->create_posts ) || ! current_user_can( 'edit_post', $id ) ) {
			wp_die( esc_html__( 'Sorry, you are not allowed to edit this item.' ) );
		}

		$copy_id = wp_insert_post(
			wp_slash(
				array(
					'post_type'      => $post->post_type,
					'post_status'    => 'draft',
					'post_title'     => $post->post_title . ' (copy)',
					'post_content'   => $post->post_content,
					'post_excerpt'   => $post->post_excerpt,
					'post_parent'    => $post->post_parent,
					'menu_order'     => $post->menu_order,
					'post_password'  => $post->post_password,
					'comment_status' => $post->comment_status,
					'ping_status'    => $post->ping_status,
					'post_author'    => get_current_user_id(),
				)
			),
			true
		);
		if ( is_wp_error( $copy_id ) ) {
			wp_die( esc_html( $copy_id->get_error_message() ) );
		}

		foreach ( get_object_taxonomies( $post->post_type ) as $taxonomy ) {
			$terms = wp_get_object_terms( $id, $taxonomy, array( 'fields' => 'ids' ) );
			if ( ! is_wp_error( $terms ) ) {
				wp_set_object_terms( $copy_id, array_map( 'intval', $terms ), $taxonomy );
			}
		}
		foreach ( get_post_meta( $id ) as $key => $values ) {
			if ( in_array( $key, array( '_edit_lock', '_edit_last', '_wp_old_slug', '_wp_old_date' ), true ) ) {
				continue;
			}
			foreach ( $values as $value ) {
				add_post_meta( $copy_id, $key, wp_slash( maybe_unserialize( $value ) ) );
			}
		}

		wp_safe_redirect( admin_url( 'post.php?action=edit&post=' . $copy_id ) );
		exit;
	}
);

Notes, where it runs and what it was tested with

Switching from the plugin

  1. Install the snippet in Snipfire (it arrives switched off).
  2. Deactivate Yoast Duplicate Post.
  3. Switch the snippet on. Snipfire checks the code and test-loads your site first.
  4. Check the page it affects, then delete the plugin.

Move your snippets over this afternoon.

Importing changes nothing until you switch over, and your old shortcodes keep working.