G Growreplies docs

WordPress & WooCommerce

Page builders

A huge portion of real-world WordPress sites use a page builder instead of vanilla Gutenberg. The Pitchbar plugin renders each supported builder's pages with the builder's own native API before sending HTML to your Pitchbar agent, so the synced content is what the visitor actually sees.

This page explains how detection + rendering works, which builders are supported, and how to override the behaviour with a filter.

Why this matters

Most page builders don't store the rendered page in post_content. They keep the layout tree in postmeta and recompose the HTML at render time. If your sync pipeline naively reads post_content and calls apply_filters('the_content', …), you'll get:

  • Elementor: empty string. The layout lives entirely in _elementor_data postmeta.
  • Bricks: empty. Layout in _bricks_page_content_2.
  • Oxygen: a single [oxygen_html] shortcode stub. The real layout is in ct_builder_shortcodes postmeta.
  • Beaver Builder: a stripped-down "fallback" HTML if the builder never gets a chance to override.
  • Divi: the shortcode-encoded layout in post_content. It only renders correctly when the post loop is properly primed.

The plugin's PageBuilderContent helper handles each case explicitly. First match wins; a post that isn't owned by any detected builder falls through to the vanilla the_content path.

Supported builders

Builder Detection postmeta Renderer
Elementor (Free + Pro) _elementor_edit_mode = builder \Elementor\Plugin::$instance->frontend->get_builder_content_for_display($id, true)
Beaver Builder _fl_builder_enabled = 1 FLBuilder::render_content_by_id($id)
Oxygen Builder ct_builder_shortcodes (non-empty) do_shortcode($shortcodes)
Bricks Builder _bricks_page_content_2 (non-empty) AND BRICKS_VERSION defined \Bricks\Frontend::render_content($payload)
Divi _et_pb_use_builder = on Routes through the_content filter (with setup_postdata primed)

Every renderer is wrapped in try/catch (Throwable). A throwing builder renderer falls through silently to the next detection or to the vanilla path, so an upgraded builder version that changes its internal API can never fatal-out the sync.

Why Divi is different

Divi does store its layout in post_content — as shortcode-encoded markup like [et_pb_section][et_pb_row][et_pb_column][et_pb_text]…. The shortcodes resolve correctly through the WordPress filter chain only when the post loop is primed: $GLOBALS['post'] set and setup_postdata() called.

Plenty of third-party plugins (Yoast, Jetpack, embed handlers) also gate their the_content callbacks on a valid $post global. Without postdata priming, every one of them early-returns and the synced HTML is missing the chrome that makes the page actually useful.

The fix shipped in plugin v2.0.0: PostContentExtractor always primes $GLOBALS['post'] + calls setup_postdata() around the filter chain, wraps the work in try/finally, and restores $GLOBALS['post'] + calls wp_reset_postdata() even if a filter throws.

What the wire looks like

For an Elementor page titled "Pricing", the plugin sends the fully-rendered HTML to /api/v1/wp/posts/sync exactly as the browser would receive it — including section wrappers, widget HTML, and Elementor's own classnames. Pitchbar then strips tags server-side for chunking, so the classnames don't end up in the embedding.

Sample sliced payload (truncated for readability):

{
  "wp_id": 142,
  "post_type": "page",
  "permalink": "https://shop.example/pricing",
  "title": "Pricing",
  "content_html": "<div class=\"elementor elementor-142\"><section class=\"elementor-section …\">…</section>…</div>",
  "excerpt": "Three plans, two outcomes…",
  "content_hash": "ab12…ef90",
  "modified_at": "2026-05-09T14:30:00+00:00",
  "language": "en-us",
  "taxonomy_terms": []
}

Override the rendered HTML

The pitchbar_post_content_html filter receives the final HTML after the builder renderer runs (or after the vanilla filter chain runs, for non-builder posts) — so you can post-process it without caring which builder owned the page:

add_filter('pitchbar_post_content_html', function ($html, $post, $builder) {
    // $builder is one of: 'elementor', 'beaver', 'oxygen', 'bricks',
    // 'divi', or null for plain Gutenberg/Classic posts.

    if ($builder === 'elementor') {
        // Strip Elementor's helper iframes that crawlers don't see.
        $html = preg_replace('#<iframe[^>]*data-elementor-[^>]*>.*?</iframe>#is', '', $html);
    }

    return $html;
}, 10, 3);

Returning an empty string suppresses sync for that post (Pitchbar will accept the empty content but the agent won't have anything to retrieve from). Returning null short-circuits the rest of the filter chain.

When a builder upgrades and breaks

Page builders periodically rename their internal renderers — when that happens, the plugin's reflection-based call falls through to the next path and the post syncs as if it were a vanilla Gutenberg post. The result is degraded (you may get an empty string or a shortcode stub), but the sync itself doesn't fatal out.

If you notice a specific builder version producing empty content, open an issue against the Pitchbar repo with the builder name + version. The fix is usually a single-line update to the renderer call in PageBuilderContent.

Limitations

  • Dynamic data widgets. Elementor Pro's "Dynamic Tags" that pull live data (cart counts, user names) render with their default fallback when there's no real visitor in scope.
  • Conditional display rules. Visibility rules that depend on the requesting visitor (logged-in only, geolocation, A/B variants) resolve against the sync request context, which is server-side. Sections gated to "logged-in users only" are not synced.
  • Lazy-loaded inline blocks. JavaScript-only content (React micro-frontends embedded as Elementor HTML widgets) never executes server-side, so the synced HTML reflects the placeholder, not the hydrated content.

These are inherent to server-side rendering of a builder layout and apply to every CMS plugin that does the same thing (Yoast SEO sitemap generators, schema markup plugins, and so on).