Why duplicating a page breaks your Elementor layout (and your ACF fields)
A duplicated page that looks fine but renders wrong is almost always one bug: WordPress strips a level of backslashes on every copy. Here is the mechanism, and how to test your own site in a minute.
You duplicate a page. The copy appears, the title is right, the content is there. You open it in
the builder and the layout is wrong — a section collapsed, or the whole canvas blank. Or the page
is fine but an ACF repeater came back empty. Or a code block renders \n as two literal
characters where the original showed a line break.
These look like four different bugs. They are one, and it is not in your builder.
Every copy eats one level of backslashes
WordPress has an old and genuinely confusing convention. wp_insert_post() and add_post_meta()
both expect slashed input, and they call wp_unslash() on whatever you give them. Values you
read back out of the database are already unslashed.
So the obvious implementation of a duplicate is wrong in a silent way:
// Wrong. Loses one level of backslashes on every field, every time.
$value = get_post_meta( $source_id, $key, true );
update_post_meta( $copy_id, $key, $value );
The read gives you an unslashed string. The write unslashes it again. One level of escaping disappears, with no error and no warning.
On prose this is invisible — nobody has a backslash in a paragraph. It becomes visible the moment a field holds structured data, where backslashes are not decoration but syntax:
_elementor_datais JSON, which leans on backslashes everywhere: escaped quotes, escaped newlines, the escaped forward slashesjson_encode()puts in every URL. Strip one level and you do not get slightly wrong JSON — you get JSON that fails to parse, which is why the canvas comes back empty rather than misaligned.- ACF repeaters and flexible content store serialized arrays whose declared string lengths must
match their contents. Remove a character and
unserialize()returnsfalse. The field renders as nothing. - Code blocks and regex fields hold backslashes as literal content.
\dbecomesd.
The fix is one function call on every write:
add_post_meta( $to_id, $meta_key, wp_slash( maybe_unserialize( $meta_value ) ) );
wp_slash() adds the level that add_post_meta() is about to remove, so the round trip lands on
the identical stored string. Same rule for post_content and post_content_filtered.
It compounds. Duplicate a page, then duplicate the copy, and you have lost two levels. Teams that build each new landing page from last month’s landing page are running this loop indefinitely. The first few generations look fine. Somewhere around the third, a page stops rendering — and nothing points at the duplicate button.
Test your own site in a minute
Put a string with a real backslash in a custom field — a\b is enough — duplicate the post, and
check the copy’s value. If it comes back ab, your duplicate tool has this bug, and every
structured field on every copy it has ever made is affected.
Adminkeep’s Duplicate feature is built around this rule — every write passes through
wp_slash(), and meta is read raw so multi-value keys survive in order. No dashboard ads, no
upgrade nags; the absence of them is enforced by a unit test rather than promised on a landing
page.
If you are still deciding whether you need a plugin at all, the guide to duplicating a page in WordPress shows the manual method and exactly what it leaves behind.