How to Add a “Duplicate Post” Feature to WordPress (No Plugins Needed)

Gerry Manzari · March 30, 2026 · 4 min read

As a WordPress developer, I spend a lot of time duplicating posts and pages. Whether I’m creating similar blog posts, copying page layouts, or testing content—WordPress doesn’t have a built-in duplicate feature, and I don’t like to use bloated plugins for this simple task.

In this tutorial, I’ll show you how to add a “Duplicate” button directly to your WordPress post list with just pure PHP code. This solution will copy everything—content, metadata, taxonomies, featured images—while being smart enough to avoid common pitfalls like conflicting slugs, broken ACF fields, or Elementor data corruption.

Code Vs. Plugins:

I’ve tried the popular plugins—Duplicate Post, Yoast Duplicate, and others. Here’s why I moved away from them:

  • Performance bloat — They add database queries on every post load
  • Outdated code — Many haven’t been updated in years
  • Unnecessary features — Bulk duplication, clone settings, custom options I’ll never use
  • Plugin vulnerabilities — More code = more attack surface
  • Maintenance overhead — Another plugin to update and monitor

I realized a simple, lean PHP solution handles 99% of my use cases better than any plugin.

PHP Code:

Add this to your theme’s functions.php file or create a must-use plugin (I recommend the must-use plugin approach):

PHP
/**
 * Duplicate post as draft (STABLE VERSION)
 */
add_action( 'admin_action_rd_duplicate_post_as_draft', function() {

    if ( ! isset( $_GET['post'], $_GET['duplicate_nonce'] ) ) {
        wp_die( 'Invalid request.' );
    }

    $post_id = absint( $_GET['post'] );

    // Verify nonce
    if ( ! wp_verify_nonce( $_GET['duplicate_nonce'], 'rd_duplicate_post_' . $post_id ) ) {
        wp_die( 'Security check failed.' );
    }

    $post = get_post( $post_id );

    if ( ! $post || ! current_user_can( 'edit_post', $post_id ) ) {
        wp_die( 'Post not found or permission denied.' );
    }

    // Create duplicate post
    $new_post_id = wp_insert_post( [
        'comment_status' => $post->comment_status,
        'ping_status'    => $post->ping_status,
        'post_author'    => get_current_user_id(),
        'post_content'   => $post->post_content,
        'post_excerpt'   => $post->post_excerpt,
        'post_name'      => '', // Let WP generate unique slug
        'post_parent'    => $post->post_parent,
        'post_password'  => $post->post_password,
        'post_status'    => 'draft',
        'post_title'     => sprintf( '%s (Copy)', $post->post_title ),
        'post_type'      => $post->post_type,
        'menu_order'     => $post->menu_order
    ] );

    if ( is_wp_error( $new_post_id ) || ! $new_post_id ) {
        wp_die( 'Failed to create duplicate post.' );
    }

    // Copy taxonomies
    $taxonomies = get_object_taxonomies( $post->post_type );
    foreach ( $taxonomies as $taxonomy ) {
        $terms = wp_get_object_terms( $post_id, $taxonomy, [ 'fields' => 'ids' ] );
        if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
            wp_set_object_terms( $new_post_id, $terms, $taxonomy );
        }
    }

    // Copy meta (safe approach)
    $meta = get_post_meta( $post_id );

    $skip_meta = [
        '_wp_old_slug',
        '_edit_lock',
        '_edit_last',
        '_wp_trash_meta_status',
    ];

    foreach ( $meta as $key => $values ) {
        if ( in_array( $key, $skip_meta, true ) ) {
            continue;
        }

        foreach ( $values as $value ) {
            add_post_meta( $new_post_id, $key, maybe_unserialize( $value ) );
        }
    }

    // Copy featured image
    $thumbnail_id = get_post_thumbnail_id( $post_id );
    if ( $thumbnail_id ) {
        set_post_thumbnail( $new_post_id, $thumbnail_id );
    }

    // Redirect to edit screen
    wp_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );
    exit;
} );


/**
 * Add Duplicate link
 */
add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );
add_filter( 'page_row_actions', 'rd_duplicate_post_link', 10, 2 );

function rd_duplicate_post_link( $actions, $post ) {

    if ( current_user_can( 'edit_post', $post->ID ) ) {

        $url = wp_nonce_url(
            admin_url( 'admin.php?action=rd_duplicate_post_as_draft&post=' . $post->ID ),
            'rd_duplicate_post_' . $post->ID,
            'duplicate_nonce'
        );

        $actions['duplicate'] = '<a href="' . esc_url( $url ) . '">Duplicate</a>';
    }

    return $actions;
}

Add To Your Site:

I recommend installing this as a must-use plugin so it doesn’t get wiped out by theme updates, but to add it quickly to your website, just follow these steps:

  • Go to Appearance → Theme Code Editor
  • Open functions.php
  • Paste the code at the bottom
  • Click Update File

What My Code Does:

  • “Duplicate” button in post/page list view
  • Smart slug handling — Auto-generates unique permalinks
  • Complete meta copying — Elementor, ACF, custom fields all preserved
  • Taxonomy support — Categories, tags, custom taxonomies all copied
  • Featured image duplication — Thumbnails copied automatically
  • WPML support — Handles multilingual sites correctly
  • Security verified — Nonce protection + permission checks
  • Draft status — Opens as draft for editing before publishing