Build a page in Elementor. Add a Heading element with the words *"Italian leather handbag — handmade in Florence"*. Hit Save. Then go to the front end of your site, open the WordPress search, and type *"leather handbag"*.
The page doesn't show up.
This happens because page builders store user-authored content in postmeta — not in wp_posts.post_content. Default WordPress search (and most third-party search plugins) read post_content only. Your headings, text blocks, button labels, captions, and alt text — everything you typed into the visual editor — sit in postmeta and are invisible to search.
This post explains why that happens, which builders are affected, and how Queryra solves it through AI semantic search that reads each builder's storage format directly.
Where the content actually lives
WordPress's database schema is built around two main tables for post data:
wp_posts— the canonical table.post_title,post_content,post_excerpt,post_status, and a few timestamps. This is whatthe_content()and default search read.wp_postmeta— a flat key-value table attached to each post. Plugins use it for *anything* that doesn't fit the canonical fields — SEO meta titles, internal flags, layout JSON, custom field values, page builder structures.
The original WordPress assumption was that real user-authored content goes into post_content. Plugins use postmeta for *metadata about* content. Page builders break this assumption: they treat post_content as essentially a placeholder, and put the actual page content into postmeta as a serialised structure that the builder parses at render time.
Why? Because the builders need rich structured data — nested sections, columns, widgets, styling — and post_content (a single HTML/text field) can't represent that cleanly. So they bypass it.
The price: anything that reads post_content to find text — default WordPress search, most search plugins, RSS feeds, third-party content tools — sees an empty or near-empty field for builder-authored pages.
Per-builder storage details
Each major page builder uses its own postmeta key (or set of keys). Here's the landscape:
### Elementor
- Key:
_elementor_data - Format: JSON tree representing every element (section, column, widget) with their settings and content
post_content: typically empty or a small placeholder- Render: Elementor hooks
the_contentfilter to parse_elementor_dataand emit HTML at request time
### Breakdance
- Key:
_breakdance_data - Format: JSON tree, similar conceptual structure to Elementor
post_content: contains only a 49-byte Gutenberg block launcher comment (<!-- wp:breakdance/block-breakdance-launcher /-->)- Render: Breakdance hooks
the_contentfilter with priorityPHP_INT_MINto replace the placeholder with rendered HTML
### Oxygen (Classic)
- Keys:
_ct_builder_shortcodes(rendered HTML) +_ct_builder_json_v2(structured tree) - Format: Dual storage — both the rendered HTML and the structured tree are kept; the structured tree is canonical, the HTML is a cache
post_content: typically empty or a small Gutenberg paragraph block- Render: Oxygen renders from its own template engine, bypassing the WordPress theme
- Note: The
_ct_prefix is a legacy from Oxygen's original "Component Tester" name
### Beaver Builder
- Key:
_fl_builder_data - Format: Serialised PHP (not JSON) — Beaver Builder uses PHP's native
serialize()for storage post_content: depends on settings; sometimes contains rendered output, often empty- Render: Beaver Builder parses the serialised data and emits HTML
### Bricks Builder
- Keys:
_bricks_*prefix family - Format: JSON structured tree
post_content: typically empty- Note: Queryra doesn't currently auto-extract Bricks content. Workaround via the [
queryra_indexable_meta_content](/blog/queryra-developer-filters) developer filter.
The pattern is consistent: builder content lives in postmeta, sometimes spread across multiple keys, in a format the builder understands but generic search doesn't.
What Queryra does about it
Queryra reads each builder's postmeta directly during sync — not at search time, and not through the the_content filter (which would require running every page through PHP rendering, which is expensive and side-effect-prone). The flow:
- Detection. On sync, the plugin checks for each builder's signature postmeta key on every post. If present, builder mode is active for that post.
- Extraction. For each builder:
- - Elementor:
json_decode(_elementor_data)→ recurse the tree, pull everyeditor,title,text,link_textfield - - Breakdance: same approach against
_breakdance_data - - Oxygen: prefer
_ct_builder_json_v2(structured); fall back to_ct_builder_shortcodes(HTML) if needed - - Beaver Builder:
unserialize(_fl_builder_data)→ walk modules, pull text/heading/HTML fields - Cleaning. Extracted text gets
wp_strip_all_tags()plus a filter that drops obvious non-content (CSS class names, hex colors, URLs, ISO dates). - Append. Cleaned text goes into the record's
descriptionfield that's sent to the Queryra API. - Embed. The Queryra backend generates an AI embedding from the full content — title, original
post_content, excerpt, taxonomies, plus the extracted builder content. Search now finds the page by its real visible text, not just its title.
No configuration. No per-builder settings. The plugin auto-detects what's installed and reads accordingly.
What this fixes
Concrete failures of default WordPress search that Queryra now closes:
- Elementor sites: Heading widgets, Text Editor widgets, button labels, image alt text — all readable. Search for a phrase that appears in your hero heading on a landing page now works.
- Breakdance sites: Same — every text element in the builder is findable.
- Oxygen sites: Including the *Classic* line, which has the dual-storage quirk.
- Beaver Builder sites: Including all module types that hold text (Heading, Text Editor, Button, Callout, Posts modules with custom labels).
- WooCommerce stores using a builder for product pages: Product descriptions authored in the builder are searchable alongside (or instead of) the short/long description fields.
- Knowledge bases and documentation sites built with builders: Headings and body text of help articles become findable through AI semantic search —
"how do I reset my password"matches a builder-built page whose heading says "Password reset instructions".
What this doesn't do
Honest framing:
- Bricks Builder is not auto-extracted. Same architectural pattern as the others, but not on the auto-detection list yet. Workaround: hook into
queryra_indexable_meta_contentfilter and read_bricks_*postmeta manually — see the developer filters guide. - Dynamic content rendered at request time is missed. If your builder content includes shortcodes that compute values at render time (e.g.
[current_year],[user_specific_recommendations]), the indexed version stores the shortcode text, not the rendered value. This matches default WordPress search behaviour. - Theme builder templates aren't relevant. Builders use "theme builder" templates to define how *single posts* look. Those templates live in a different post type and aren't synced to Queryra — but the content they wrap (the actual
post_contentand postmeta of the displayed post) is. - Computed product fields: Some builders show WooCommerce computed values (current price, stock status). Those aren't part of the page builder structure — Queryra reads them via the WooCommerce native fields instead.
- CSS / styling text: Class names, hex colors, font sizes — explicitly filtered out. We index visible content only.
For developers extending the integration
If you have a custom page builder or builder-like plugin that uses its own postmeta keys, the [queryra_indexable_meta_content](/blog/queryra-developer-filters) filter lets you hook in and append any text source to the indexable content. Pattern:
