If you have ever tried to move a page builder site somewhere else, you already know the sinking feeling. You open Tools → Export, download the XML, and the text you can clearly see on screen is nowhere in the file. Feed that export into a headless framework, a static site generator, or another WordPress install and you get blank pages.
This guide explains exactly why it happens, what a portable export has to contain instead, and how to get an Elementor or Bricks site out as clean JSON with the content, SEO, and media intact.
Why the standard WordPress export looks empty
When you write in the classic editor or in Gutenberg, your words go into a database column called post_content. The built-in exporter reads that column, so what you see is what you get.
Page builders work differently. Elementor keeps its layout in a hidden meta field called _elementor_data, stored as a long serialized JSON blob. Bricks does the same thing in its own meta field. The visible post_content for those pages is often blank or holds a single placeholder. So when the exporter reads post_content, there is genuinely nothing there to read.
If you look at what the builder actually stores, it is not your content, it is a set of instructions for how to draw your content:
// _elementor_data, simplified. This is what the builder stores,
// not the text a visitor reads.
[
{ "elType": "section", "elements": [
{ "elType": "widget", "widgetType": "heading",
"settings": { "title": "Corneal Cross-Linking" } },
{ "elType": "widget", "widgetType": "text-editor",
"settings": { "editor": "<p>A minimally invasive...</p>" } }
] }
]
That blob is not useless, but it is only useful to the exact builder that created it, on a site with the same builder, the same version, and the same custom widgets installed. Give it to a headless framework or a plain importer and it means nothing. You need the rendered result, the actual HTML a visitor sees, not the builder’s internal drawing instructions.
What a clean export actually needs
Getting real content out of a page builder site means rendering each page the way the browser does, through the builder’s own engine, and then capturing the output. A JSON export that is genuinely portable should hold more than body text. For every page and post you want:
- The rendered HTML, with builder shortcodes and blocks already executed, so the content is real text and markup rather than a serialized blob.
- The title, slug, and permalink. These are what you use to build 301 redirects so you do not lose your search rankings on the new site.
- SEO meta from whatever plugin you use, whether that is Yoast, Rank Math, or All in One SEO.
- The featured image and every in-content image, with alt text and dimensions.
- Custom fields, including image fields from ACF, JetEngine, or Pods, resolved from bare attachment IDs into real media URLs.
- Categories, tags, custom taxonomies, and your navigation menus.
Miss any of these and the migration turns into weeks of manual cleanup. The images are the usual trap, because builders and custom fields often store an image as a number that points to an attachment, and that number means nothing on a new system.
A generic exporter that reads post_content will always come back empty for builder pages. The only reliable fix is to render each page through the builder that made it, then capture the HTML that comes out.
Rendering each builder correctly
The reason a single generic exporter struggles is that every builder needs a different call to produce its final HTML. There is no one function that covers them all:
// Each builder exposes its own render path.
// Elementor
$html = Elementor\Plugin::$instance->frontend
->get_builder_content_for_display( $post_id );
// Bricks
$html = \Bricks\Frontend::render_data( $elements );
// Beaver Builder
$html = FLBuilder::render_content_by_id( $post_id );
// Gutenberg, Classic, WPBakery, Divi: the standard pipeline
$html = apply_filters( 'the_content', $post->post_content );
If an export tool does not know which builder made a page, it calls the wrong method, and you are back to an empty result. The correct approach is to detect the builder per page, render it with the matching engine, and tag each item with the path that was used, so an empty render is something you can actually see and check rather than a silent failure.
The practical way to do it
You can write all of this yourself if you are comfortable in PHP and you have time to handle every builder and every edge case. Most people would rather not. This is exactly the gap the free plugin Migratik was built to fill.
Built for exactly this problem
Migratik detects the builder on every page and renders Elementor, Bricks, Beaver Builder, Oxygen, WPBakery, Divi, and Gutenberg each with the right engine, so the content comes out as real HTML instead of a serialized blob. It packs the whole site into one clean JSON file, and each item is tagged with the render path used, so an empty render is visible rather than silent. It is read-only, so it is safe to run on the live site you are migrating.
The workflow is short. Install the plugin on the site you are exporting, open the Migratik screen, review the counts of what will be packed, and click one button to download a single JSON file. Because it started life moving a real WPBakery site onto a Next.js and Supabase stack, the awkward parts, the builder rendering and the custom-field image resolution, are already handled.
What you end up with
The result is one JSON file where every item carries its rendered content_html, its raw content_raw, the title and slug and permalink you need for redirects, the SEO meta, the featured and in-content images with alt text, the ACF and JetEngine and Pods fields with image fields already resolved to real media objects, the taxonomies, and your menus. That is a file you can hand to a developer, feed into a static site generator, or import into another system, and everything that mattered on the old site is still there.
From there it depends on where you are going. If your destination is a modern framework, our step-by-step guides pick up where the export leaves off: migrating to Next.js, migrating to Astro, or moving to a headless CMS, all without losing your SEO. If your destination needs a specific shape, Migratik Pro converts the same capture into Markdown or MDX, WXR, or CSV. The free tier captures everything and never expires, so you can see your full export before deciding whether you need any of that.
Frequently asked questions
Why is my WordPress export empty for Elementor or Bricks pages?
Page builders store their layout in hidden meta fields, not in the post_content column that the built-in exporter reads. Elementor keeps its data in _elementor_data and Bricks in its own meta field, so the visible post_content is often blank. The exporter reads an empty column and hands you an empty page.
How do I export an Elementor or Bricks page as JSON?
Render each page through the builder’s own engine and capture the output, rather than reading post_content. A free tool like Migratik detects the builder per page, renders it with the correct method, and hands you one clean JSON file with the real content, SEO meta, and media.
Does the export keep my SEO metadata and images?
It should. A good export carries the title, slug, and permalink for redirects, the SEO meta from Yoast, Rank Math, or All in One SEO, the featured and in-content images with alt text, and custom fields with image fields resolved from attachment IDs to real media URLs.
Is it safe to run on a live production site?
Yes, as long as the tool is read-only. Migratik only reads your content and returns a file. It never writes to your posts or database, so it is safe on a live site, and it leaves nothing behind when you delete it.
Get your builder site out, in full
Migratik renders Elementor, Bricks, and every major builder through its own engine, then packs the whole site (content, SEO, media, and custom fields) into one clean JSON file. Free to install, read-only, and safe on a live site.