Skip to content

Bloomreach to WordPress Migration: A Technical Guide

A technical guide to migrating from Bloomreach (SaaS and PaaS) to WordPress — covering content export, model mapping, media handling, and URL redirects.

Abdul Aleem Abdul Aleem · · 20 min read
Bloomreach to WordPress Migration: A Technical Guide
TALK TO AN ENGINEER

Planning a migration?

Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.

Schedule a free call
  • 1,500+ migrations completed
  • Zero downtime guaranteed
  • Transparent, fixed pricing
  • Project success responsibility
  • Post-migration support included

Bloomreach to WordPress Migration: A Technical Guide

Migrating from Bloomreach to WordPress means extracting content from a JCR-based repository or headless SaaS instance, transforming it to match WordPress's flat post/page model, and importing it without losing URLs, metadata, or media. This is not a one-click export.

Bloomreach stores content as typed documents in a tree structure, delivered via JSON APIs to a decoupled frontend. WordPress uses a relational database (MySQL), Custom Post Types (CPTs), and either the Gutenberg block editor or Advanced Custom Fields (ACF) for content management. The mismatch between these two models is where most migration complexity lives.

This guide covers both Bloomreach Content (SaaS) and Bloomreach Experience Manager (PaaS/Self-Hosted) because the export paths differ significantly.

Why Teams Move from Bloomreach to WordPress

The most common reasons:

  • Cost reduction. Bloomreach licensing — especially Discovery + Content bundles — can run six figures annually. WordPress hosting and plugin costs are a fraction of that, significantly lowering your total cost of ownership.
  • Editorial independence. Bloomreach marketing teams working with complex websites are often bottlenecked by dependence on in-house engineering teams and the legacy Java-based CMS — every minor change requires developer involvement, slowing down the content pipeline.
  • Ecosystem breadth. WordPress powers 42.4% of all websites and holds 59.8% of the CMS market share. It has 60,000+ plugins, WooCommerce for commerce, and a developer pool that dwarfs Bloomreach's.
  • Simplifying the stack. If you're not using Bloomreach Discovery or Engagement, you're paying for a platform whose advantages you don't need.

That said, moving away from Bloomreach means losing built-in personalization, faceted navigation backed by JCR queries, and native headless architecture. Make sure you actually need less before committing.

Understand Which Bloomreach You're On

This matters more than people realize. Bloomreach Experience Manager (brXM) is the PaaS or self-hosted Content solution, with versions 14 through 17. Bloomreach Content (SaaS) is a separate product. The export mechanisms are different:

Factor Bloomreach Content (SaaS) Bloomreach Experience Manager (PaaS/Self-Hosted)
Content storage Cloud-hosted, API-accessible JCR content repository (Apache Jackrabbit)
Primary export method Content Batch Export API JCR export (XML/YAML) or Content REST API
Image/asset export Not supported via batch API Direct filesystem/repository access
Authentication Token-based Management API auth Token-based or direct repository access
API version note N/A brXM 15 serves Delivery API 1.0; 14.x defaults to 0.9

Under the hood, Bloomreach Experience Manager's content model is based on a layered architecture. The foundation is a JCR-compliant content repository, in which content is stored as properties of nodes in a tree structure. In the repository, editable documents live under /content/documents, images under /content/gallery, assets under /content/assets, and site pages, components, templates, channels, and URL configuration live in HST configuration. Exporting just documents is not a full site migration.

Step 1: Audit and Map Your Content Model

Before touching any code, document every content type in your Bloomreach instance.

A document type defines the data structure and the editing template of a class of documents. A document type can contain both primitive and compound type fields. A field group type defines the data structure and the editing template of a reusable block of document fields. An instance of a field group type can only exist within a document instance.

Experience Pages in brXM are distinct from reusable documents: they are page-level containers that assemble channel components, experience components, and content items into a rendered page. They are not simple documents with body fields. When mapping these to WordPress, you're splitting a structural concern (page layout and component arrangement) from an editorial concern (page copy and metadata). Map the copy and metadata to WordPress page fields or ACF; rebuild the layout using WordPress page templates, Full Site Editing templates, or locked block patterns.

Map each Bloomreach object to a WordPress equivalent:

Bloomreach source object Typical WordPress target Notes
Reusable document (article, blog post) Post or Custom Post Type Keep source UUID/path in post meta for idempotent reruns
Static page WordPress page type
Experience page Page + template/pattern/locked block layout Split page structure from page copy where possible
Page-specific component Block attributes or post meta on the owning page This is where missing content often hides
Menu Nav menu + menu items Build after content IDs exist
Image / asset Attachment in Media Library Download binaries separately
Resource bundle / labels Theme or plugin translation files, or site options Do not dump these into posts
Route / legacy URL Permalink rules + redirect map Treat as launch data, not editorial cleanup
Warning

Bloomreach compound types can be deeply nested. WordPress metadata is flat key-value. If you have three levels of nested compound types, you'll need to flatten them or use ACF repeater/flexible content fields, which store data as serialized arrays in wp_postmeta.

Critical wp_postmeta scaling constraint: Serialized arrays in wp_postmeta cannot be queried field-by-field with standard SQL — MySQL must deserialize the entire row to match a nested value. At scale (50,000+ posts with complex repeater data), this creates full table scans. Solutions: ACF's custom database tables extension (ACF Pro 6.1+), the Pods framework with dedicated tables, or restricting complex field queries to admin-only operations and using a search index (Algolia, Elasticsearch) for frontend filtering.

Do not attempt to map Bloomreach page components 1:1 to Gutenberg blocks programmatically. A valid Gutenberg block requires well-formed HTML wrapped in HTML comments with embedded JSON configuration — for example, <!-- wp:paragraph {"align":"left"} -->. Generating this structure programmatically from arbitrary Bloomreach component data is fragile and produces blocks that break on editor open. For complex structured data, map to ACF instead.

What to Capture in Your Audit

  • Every document type and its fields (primitive types, compound types, image links)
  • Folder structure and how it maps to categories/taxonomies
  • URL patterns — Bloomreach URLs are typically derived from folder paths and document names
  • Routes, layouts, menus, components, and templates — these are separate concerns in Bloomreach, and skipping them means discovering missing pieces during QA
  • Workflow states: a document variant is a node that represents a document in a particular workflow state such as draft, unpublished, or published. Decide which variant to migrate (usually published).
  • Multi-language structure: each translation of a document is stored as a separate document. In WordPress, handle this with WPML or Polylang. Decide the WordPress locale model before you import anything — in Bloomreach, localized folder trees and translated folder names can influence the final URL, so locale handling is part of URL mapping, not just translation workflow.

One WordPress-specific constraint: custom post type keys must not exceed 20 characters. Do not blindly reuse long Bloomreach document type names as CPT keys. (developer.wordpress.org)

Whatever target model you choose, store immutable source identifiers in WordPress meta: source UUID, repository path, content type, locale, and legacy URL. This gives you deduplication, delta imports, rollback clues, and a clean key for redirect validation. It keeps your migration rerunnable instead of turning it into a one-shot script.

Step 2: Export Content from Bloomreach

Bloomreach does not offer a one-click "export to SQL" button. The export method depends on which product you're running.

Option A: Content Batch Export API (SaaS)

The Content Batch Export API enables developers to export large amounts of content from a Bloomreach Content project.

The file format is a zip containing a JSON lines file, also known as an NDJSON or new line delimited JSON file. Each file line contains a JSON object representing a single document, page, resource bundle, or folder.

This is the cleanest export path for SaaS customers. But there is a hard limitation: the Content Batch Export API does not support images and assets. If you don't use a DAM and store images and assets in the content repository instead, you will need to export these manually.

The Content Export API is a protected management API requiring token authorization.

# Trigger a batch export for a specific content path
curl -X POST \
  'https://{environment}.bloomreach.io/management/content/v1/export' \
  -H 'Authorization: Bearer {token}' \
  -H 'Content-Type: application/json' \
  -d '{
    "sourcePath": "/content/documents/myproject",
    "contentCategories": ["document"]
  }'

Rate limits: The Bloomreach Management API enforces rate limits that vary by environment tier. Sustained export loops without backoff will trigger 429 responses. Build exponential backoff into any extraction loop, and monitor response headers for Retry-After values. For current rate limit figures, refer to the Bloomreach Management API documentation — they are tier-specific and subject to change.

Option B: Delivery API (SaaS or PaaS)

The Content REST API is a generic REST API running on top of the delivery tier, automatically exposing all published content based on the document types. For PaaS/Self-Hosted instances, this is often the most practical extraction route. The default setup exposes all content under the project's content folder to all clients.

Key constraint: the default maximum page size is 100. For sites with thousands of documents, you need pagination logic.

import requests
import json
import time
 
BLOOMREACH_API_URL = "https://{account}.bloomreach.io/delivery/site/v1/channels/{channel}/documents"
HEADERS = {
    "Authorization": "Bearer YOUR_API_TOKEN",
    "Accept": "application/json"
}
 
def fetch_bloomreach_documents(doc_type, max_retries=3):
    page = 1
    has_more = True
    all_documents = []
 
    while has_more:
        params = {
            "_nodetype": doc_type,
            "page": page,
            "pageSize": 100
        }
 
        for attempt in range(max_retries):
            response = requests.get(BLOOMREACH_API_URL, headers=HEADERS, params=params)
            if response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 10))
                print(f"Rate limited. Waiting {retry_after}s before retry.")
                time.sleep(retry_after)
                continue
            if response.status_code != 200:
                print(f"Error fetching page {page}: {response.text}")
                has_more = False
                break
            break
 
        if response.status_code != 200:
            break
 
        data = response.json()
        documents = data.get("document", [])
 
        if not documents:
            has_more = False
        else:
            all_documents.extend(documents)
            page += 1
            time.sleep(0.5)  # Conservative base delay between pages
 
    return all_documents
 
news_articles = fetch_bloomreach_documents("myproject:newsarticle")
 
with open("bloomreach_news.json", "w") as f:
    json.dump(news_articles, f, indent=2)

A major complexity: images and files in Bloomreach API responses are often returned as reference IDs (e.g., cafebabe-cafe-babe-cafe-babecafebabe), not direct URLs. Your extraction script must parse the _links object to resolve these references into actual CDN URLs. Store the resolved URLs in your intermediary JSON so the ingestion script knows where to download assets from.

When you need page context — components, menus, layout — rather than just document payloads, use the Pages endpoint, which returns the page with its components, content items, and menus in a flattened representation. (documentation.bloomreach.com)

Option C: JCR Repository Export (Self-Hosted Only)

If you have direct access to the JCR repository, you can export content as XML or YAML by right-clicking on the namespace in the CMS console and selecting XML or YAML export. YAML export for JCR nodes arrived after v11.

This gives you the rawest, most complete export — including workflow states, metadata, and internal references. But parsing JCR XML is non-trivial. The node structure includes hippo:handle wrappers, variant nodes, and Bloomreach-specific mixins that all need to be stripped or transformed.

JCR namespace reference for self-hosted exports:

JCR path Content type
/content/documents/{project} Editable documents (articles, pages, etc.)
/content/gallery/{project} Images (hippogallery: namespace)
/content/assets/{project} Binary assets (hippogallery:externallink or hippogallery:imageset nodes)
/hst:hst/hst:configurations Channel, site, and component configuration
/hst:hst/hst:sites Site definitions and mount points

Documents are represented by hippo:handle nodes, which can have multiple children (variants) representing the document in different workflow states — hippo:draft, hippo:unpublished, and hippo:published. Export only the hippo:published variant unless you specifically need draft history.

Don't Forget Site Configuration

In Bloomreach, routes, layouts, components, menus, and templates are separate concerns from document content. If you need landing pages, navigation, promos, shared widgets, or localized URLs, you need both content extraction and site-configuration extraction via the Site Management API. (documentation.bloomreach.com)

Scope this as a full rebuild of your content model, page model, navigation, assets, and URL strategy — not just copying pages from A to B.

Step 3: Transform Content for WordPress

This is the most labor-intensive step. Bloomreach's tree-structured, typed content model needs to become WordPress posts with HTML body content, flat metadata, and taxonomy terms.

Key Transformations

Rich text fields: Bloomreach stores rich text as HTML with internal link references (often hippo: prefixed). These internal links must be resolved to final URLs. Image references within rich text pointing to the Bloomreach asset repository need rewriting to new WordPress media URLs. Do not trust rich-text links blindly — Bloomreach delivery responses can rewrite internal links for SPA rendering. (documentation.bloomreach.com)

Structured fields → custom fields: Bloomreach primitive fields (String, Date, Boolean, Long, Double) map to WordPress post meta. Use ACF for a structured editing experience, or raw wp_postmeta if you're code-only. For fields that require frontend filtering or sorting, use indexed columns in a custom table rather than serialized wp_postmeta — see the scaling note in Step 1.

Taxonomy mapping: Bloomreach uses folder hierarchy and free-text keywords or managed taxonomies. WordPress uses flat or hierarchical taxonomy terms. Build a lookup table between Bloomreach folder paths/taxonomy values and WordPress term IDs.

URL slugs: Extract the final URL segment from each Bloomreach document path and set it as the WordPress post_name slug. Preserve the full old URL in a mapping file for redirects.

Dates: Map Bloomreach creation and modification dates to post_date and post_modified in WordPress to preserve content chronology.

wp_options and global settings: Bloomreach global components — shared headers, footers, global promotions, sitewide labels — have no direct WordPress equivalent. These typically map to: WordPress nav menus (for navigation), theme customizer settings or site-wide options stored in wp_options via the Settings API, ACF Options Pages for globally editable fields, or full-site editing parts (header/footer templates) in block themes. Audit global components explicitly; they are the most commonly overlooked category in migration scoping.

import json
from datetime import datetime
 
def transform_bloomreach_doc(br_doc):
    """Transform a Bloomreach document to WordPress REST API format."""
    content = br_doc.get('data', {})
 
    return {
        'title': content.get('title', ''),
        'content': rewrite_internal_links(content.get('body', '')),
        'slug': br_doc.get('name', '').lower().replace(' ', '-'),
        'status': 'draft',  # Import as draft, review, then publish
        'date': content.get('publicationDate', datetime.now().isoformat()),
        'meta': {
            'bloomreach_original_path': br_doc.get('path', ''),
            'bloomreach_source_uuid': br_doc.get('id', ''),
            'bloomreach_content_type': br_doc.get('type', ''),
            'bloomreach_locale': br_doc.get('locale', ''),
        }
    }
 
def rewrite_internal_links(html_content):
    """Replace Bloomreach internal references with placeholder URLs."""
    # Implementation depends on your link format
    # Common patterns: hippo:docbase references, binaries paths
    # Pass 1: store as-is; rewrite in Pass 2 after all posts are imported
    return html_content

Step 4: Import into WordPress

The WordPress REST API lets external applications read, create, update, and delete WordPress content using HTTP requests. Authenticate with Application Passwords (built into WordPress 5.6+) and POST to the appropriate endpoint.

import requests
import base64
 
WP_URL = 'https://your-site.com/wp-json/wp/v2/posts'
WP_USER = 'migration-user'
WP_APP_PASSWORD = 'xxxx xxxx xxxx xxxx xxxx xxxx'
 
credentials = base64.b64encode(f'{WP_USER}:{WP_APP_PASSWORD}'.encode()).decode()
headers = {
    'Authorization': f'Basic {credentials}',
    'Content-Type': 'application/json'
}
 
def import_post(transformed_doc):
    response = requests.post(WP_URL, json=transformed_doc, headers=headers)
    if response.status_code == 201:
        return response.json()['id']
    else:
        print(f"Failed: {response.status_code} - {response.text}")
        return None
Tip

Create a dedicated WordPress user with the editor role for migration. Use Application Passwords — not your admin credentials. Revoke the app password immediately after migration completes.

Practical Import Order

  1. Create taxonomies and target post types.
  2. Create or map authors.
  3. Import media and capture attachment IDs.
  4. Import documents and pages (with source UUIDs stored in meta).
  5. Attach featured images and related content references.
  6. Build menus (only after content IDs exist).
  7. Load redirects, canonical rules, and SEO fields.
  8. Run a delta pass for content changed during QA.

Bloomreach rich text fields often contain internal links formatted as reference IDs rather than relative URLs. This requires a two-pass approach:

  1. Pass 1: Import all posts and store their legacy Bloomreach IDs in a meta field (bloomreach_source_uuid). Leave internal link references as-is in post_content.
  2. Pass 2: Iterate through all imported posts, find legacy reference IDs in post_content using regex, query the wp_postmeta table for the WordPress post matching that legacy ID, and rewrite the link to the new permalink.

This approach is safe to rerun: since source UUIDs are stored in meta, Pass 2 is idempotent and can be re-executed after any content correction without creating duplicates.

WP-CLI for WXR Import

WP-CLI provides a command line interface to the WordPress Importer plugin for performing data migrations. It imports content from a given WXR file.

If you convert your Bloomreach export to WordPress eXtended RSS (WXR) XML format, wp import handles the rest:

wp import bloomreach-export.xml --authors=create

A common hybrid pattern: REST API for structured content, WP-CLI for high-volume media imports.

Info

Idempotency beats speed. A loader that can rerun safely against the same content set is more valuable than a faster script that creates duplicates on every retry. Source UUIDs in post meta make this possible.

Step 5: Migrate Media and Assets

This is where Bloomreach migrations get tricky. The Content Batch Export API does not support images and assets. You have several options:

  1. Scrape the delivery tier. If images are publicly accessible via Bloomreach's delivery URLs, download them and upload to WordPress using wp media import or the REST API's /wp/v2/media endpoint.
  2. DAM export. If you use an external DAM (Cloudinary, Bynder, etc.), export directly from the DAM and upload to WordPress — or keep the DAM with a WordPress integration plugin.
  3. Repository-level export (self-hosted). Access the JCR repository directly and export binary nodes from the hippogallery: namespace under /content/gallery.
  4. Manual export with Bloomreach support. If you store images in the content repository without a DAM, contact Bloomreach support to discuss possible alternative export options.
Info

Performance note: WordPress generates multiple thumbnail sizes (thumbnail, medium, medium_large, large, and any theme-registered sizes) synchronously for every uploaded image during wp media import. On a site with 10,000+ images, this is the dominant time cost in the migration. Strategies to reduce it: disable intermediate image generation during import via add_filter('intermediate_image_sizes_advanced', '__return_empty_array'), then regenerate in bulk using wp media regenerate after import completes. Run asset migration as a separate, asynchronous process before migrating textual content.

After uploading media to WordPress, rewrite all image URLs in your post content. Build a mapping of old_bloomreach_url → new_wordpress_url and run a find-replace across all imported post_content. Use wp search-replace with the --dry-run flag first to validate scope before committing.

Step 6: Content Freeze and Cutover Protocol

Delta imports are only useful if you know precisely what changed and when. Before finalizing QA, establish a formal content freeze protocol.

Recommended sequence:

  1. Announce freeze date to editors at least 5 business days before cutover. All content creation and editing in Bloomreach stops at an agreed timestamp.
  2. Run final bulk import after freeze timestamp, pulling only documents with modification dates after the previous import run.
  3. QA window (typically 2–5 days): editors review WordPress staging. No changes to Bloomreach during this period. Any corrections go directly into WordPress.
  4. Redirect validation (parallel to QA): automated crawl of all old URLs confirming 301s resolve correctly to new destinations.
  5. DNS cutover: switch traffic from Bloomreach-fronted infrastructure to WordPress hosting. Keep Bloomreach environment running for 2–4 weeks post-launch as a rollback target.
  6. Post-launch monitoring: 404 logs, Search Console coverage errors, and redirect chains for at least 30 days.

Document the freeze timestamp in your migration runbook. Every delta import query should use that timestamp as its lower bound. If you do not freeze, you risk a gap window where editors update Bloomreach after your final delta but before DNS switches, and those changes are lost at cutover.

Step 7: URL Redirects and SEO Preservation

This is non-negotiable. Bloomreach and WordPress use completely different URL structures.

Bloomreach URLs typically look like:

/site/blog/2024/my-article-title
/site/products/category-name/product-name

WordPress defaults:

/my-article-title/
/product/product-name/

Build the redirect map during the transformation step using the bloomreach_original_path stored in post meta.

Danger

Do not skip redirects. CMS migration case studies consistently identify failed redirects as the primary cause of post-migration organic traffic loss — drops of 40% or more within the first two weeks are documented in SEO industry post-mortems (see Google's own guidance on site moves with URL changes). Map every old URL to its new URL before go-live.

Implementation

For large redirect maps (2,000+ URLs), implement redirects at the server or CDN level. WordPress redirect plugins force PHP and MySQL to process every 404, creating performance bottlenecks at scale.

Nginx redirect map:

map $request_uri $new_uri {
    /site/blog/2024/company-update  /company-update/;
    /site/products/legacy-item-123  /shop/legacy-item/;
}
 
server {
    ...
    if ($new_uri) {
        return 301 $new_uri;
    }
    ...
}

Other options:

  • Apache .htaccess for Apache-based hosting
  • Cloudflare Bulk Redirects if fronting with Cloudflare (supports up to 10,000 redirect rules per list in the free tier)
  • WordPress redirect plugins (Redirection, Yoast Premium redirects) only for manageable volumes under 2,000 redirects

Redirect validation tooling:

  • curl -IL {url} — follows the full redirect chain and prints each hop's status code and Location header. Use this for spot-checking individual redirects.
  • httpx (Python) — supports bulk redirect validation with concurrency control; faster than sequential curl loops for large redirect maps.
  • Screaming Frog — crawl the old URL list and validate 301 destination matches expected new URL.
  • Google Search Console URL Inspection — post-launch spot check for individual high-priority URLs.

SEO Launch Checklist

Google's site-move guidance is the right baseline. (developers.google.com) Permanent 301/308 redirects do not cause PageRank loss — Google has confirmed this repeatedly. Expect temporary ranking fluctuations; for medium-sized sites, Google typically processes the new URL signals within 2–8 weeks, though complex sites with many redirects can take longer.

  • Freeze URL decisions before theme QA ends.
  • Remove any staging noindex or robots restrictions before launch.
  • Load the exact old-to-new 301 map at the web server, CDN, or edge layer.
  • Publish updated XML sitemaps with the new URLs.
  • Watch Search Console and 404 logs daily after cutover.
  • Preserve SEO fields as first-class migration data: title tags, meta descriptions, canonicals, robots directives, Open Graph fields, and schema markup.

Teams lose rankings less from the CMS switch itself than from silently dropping surrounding metadata.

Step 8: Validate and QA

Post-import validation checklist:

  • Content completeness: Compare document counts between Bloomreach export and WordPress. Every document should be accounted for.
  • Rich text fidelity: Spot-check 10–20% of imported posts for broken HTML, missing images, and dead internal links.
  • Metadata integrity: Verify custom fields imported correctly. Check dates, authors, categories.
  • Media audit: Crawl the WordPress site for broken images (Screaming Frog or a broken-link scanner). Confirm no images still point to the legacy Bloomreach CDN.
  • Complex components: Check tables, accordions, and related-post grids to ensure JSON-to-HTML or JSON-to-ACF transformations worked correctly.
  • Redirect verification: Test every redirect using curl -IL or bulk redirect checker (httpx). Confirm no redirect chains longer than one hop.
  • SEO parity: Compare meta titles, descriptions, canonical URLs, and structured data between old and new.
  • Multi-language check: If using WPML/Polylang, verify every translation is linked to its parent document correctly.
  • wp_postmeta integrity: For ACF repeater fields, query a sample of records directly in the database and verify serialized array structure is intact. Use wp post meta list {post_id} to inspect.
  • Global components: Verify nav menus, headers, footers, and sitewide option values are correctly loaded in WordPress — these are often missed until UAT.

After initial QA, run a delta import — a second pass that moves only content changed since the first bulk load. This lets editors keep working in Bloomreach during the QA window without losing updates at cutover.

Common Failure Modes

Nested compound types silently flatten. Bloomreach compound types can nest three or four levels deep. If your transformation script doesn't handle recursion, you'll lose nested data without any error. Add an assertion or logging step that counts fields before and after transformation for each document type.

Rich text image references break. Bloomreach rich text often contains relative image paths or hippo:docbase references that resolve at render time. These won't work in WordPress. Every image reference in every rich text field needs resolution before import. A regex scan of your raw export JSON for hippo: prefixes will reveal the scope of this problem.

Workflow variants cause duplicates. Documents are represented by hippo:handle nodes, which can have multiple children (variants) representing the document in different workflow states — draft, unpublished, and published. If you export without filtering for the hippo:published variant, you'll import draft and unpublished versions as separate posts.

Rendered HTML treated as source of truth. Delivery HTML can contain rewritten internal links or resource URLs built for SPA rendering, and page-specific component content can live outside reusable documents. Always extract from the structured API response, not the rendered output.

Site configuration not extracted. Routes, layouts, menus, pages, components, and templates are separate concerns in Bloomreach. If you don't export them, you discover the missing pieces during UAT, not during development.

Character encoding mismatches. JCR stores content as UTF-8, but some XML exports can introduce encoding issues. Validate encoding before import using a tool like file --mime-encoding on exported files. In Python, always open files with encoding='utf-8' explicitly rather than relying on system defaults.

wp_postmeta query performance degradation at scale. Large volumes of ACF repeater data stored as serialized arrays in wp_postmeta create slow meta_value LIKE '%...%' queries. This manifests as acceptable performance in a staging environment with 1,000 posts but degraded admin performance at 50,000+ posts. Identify fields that need to be queryable or sortable before import, and route those to indexed columns rather than serialized meta.

Content freeze gaps. Editors who continue working in Bloomreach after the final delta import — even for a few hours — will have their changes silently dropped at cutover. Enforce the freeze timestamp operationally, not just as guidance.

Replacing Bloomreach Discovery and Engagement

Bloomreach is rarely used just as a CMS. If your site depends on Bloomreach Discovery or Engagement, treat those as separate workstreams — they do not migrate with page content.

Bloomreach Discovery (search and merchandising): WordPress native search relies on basic SQL LIKE queries against post_title and post_content. This does not replicate Discovery's relevance ranking, typo tolerance, synonym handling, or merchandising rules. Replacement options:

  • Algolia — the closest functional equivalent for hybrid or headless WordPress setups. Algolia's InstantSearch libraries, typo tolerance, synonym configuration, and merchandising dashboard cover most Discovery use cases. WooCommerce-specific merchandising requires Algolia's commercial plan.
  • ElasticPress — connects WordPress directly to Elasticsearch via the ElasticPress plugin. Supports faceted search, weighted fields, and morphological analysis. Self-hosted Elasticsearch requires infrastructure management; Elasticpress.io is the managed option.
  • Typesense — open-source alternative to Algolia with a self-hosted option and comparable InstantSearch compatibility.

Bloomreach Engagement (CDP and marketing automation): Engagement is a standalone CDP and does not touch CMS content. Route behavioral data to a CDP that integrates with WordPress: Segment (with WordPress plugin for event tracking), HubSpot, or Klaviyo (particularly relevant if moving to WooCommerce).

WordPress multisite for multi-brand or multi-locale Bloomreach instances: If your Bloomreach instance serves multiple channels, locales, or brands under one project, WordPress Multisite is the closest structural equivalent. Each subsite maintains its own content tables, user roles, and theme — allowing brand-level independence while sharing a single WordPress installation and plugin set. Factor multisite into your architecture decision before import, because retrofitting it after content is imported requires a full database restructure.

When This Migration Doesn't Make Sense

Be honest about these scenarios:

  • You rely heavily on Bloomreach Discovery or Engagement. These are genuinely differentiated products. Moving your CMS while keeping Discovery creates a complex integration layer.
  • You need headless delivery to multiple channels. Bloomreach Content SaaS is a true headless CMS with a structured content type editor and delivery API designed for multi-channel output. WordPress can do headless via WPGraphQL or the REST API, but the block editor and page builder ecosystem are optimized for traditional rendering, not structured content delivery.
  • Your content model is deeply structured with complex personalization rules. WordPress can handle structured content with ACF or Pods, but the native content type editing experience is weaker than Bloomreach's visual document type editor.
  • Compliance requirements mandate granular access control. Bloomreach's role-based access control on content nodes is more granular than WordPress's native five-role model. WordPress capabilities can be extended with plugins, but achieving field-level or node-level access control requires significant custom development.
  • You have 100,000+ posts with complex ACF repeater data. At this volume, wp_postmeta serialization becomes a genuine architectural problem, not a tuning problem. Evaluate whether WordPress's data model can support your query patterns before committing.

Making It Happen

A Bloomreach-to-WordPress migration is a one-way door. The content model mismatch, the media export gaps, and the URL restructuring compound into a project that's easy to underestimate.

The things that matter most, in order: a complete content audit before you write a line of code; a formal content freeze protocol before QA begins; and a tested redirect map before you flip DNS. Get those three right, and everything else is execution — script the extraction with exponential backoff on rate limits, preserve source identifiers in post meta, validate every URL with automated tooling, and keep rollback paths boring.

Frequently Asked Questions

Can you export images from Bloomreach using the Batch Export API?
No. Bloomreach's Content Batch Export API does not support images and assets. If you store images in the content repository rather than an external DAM, you'll need to export them manually — either by scraping the delivery API, accessing the JCR repository directly (self-hosted only), or working with Bloomreach support.
What format does Bloomreach export content in?
The Content Batch Export API exports a ZIP file containing an NDJSON (newline-delimited JSON) file, where each line is a JSON object representing a single document, page, resource bundle, or folder. For PaaS/self-hosted instances, you can also export JCR nodes as XML or YAML.
Will I lose SEO rankings migrating from Bloomreach to WordPress?
Only if you skip URL redirects. Bloomreach and WordPress use completely different URL structures. Create 301 redirects for every old URL pointing to its new WordPress equivalent before switching DNS. With proper redirects, metadata migration, and content parity, rankings should recover within a few weeks.
Is there a direct Bloomreach to WordPress importer?
No. Bloomreach exposes documents, pages, routes, menus, and assets through separate APIs and data models, so most teams build a custom extraction and load pipeline using the REST API, Batch Export API, or JCR export depending on whether they're on SaaS or PaaS.
How long does a Bloomreach to WordPress migration take?
It depends on content volume, the number of custom document types, and multi-language requirements. A site with 500 pages and simple document types can be migrated in 1–2 weeks. Complex sites with 1,000+ pages, nested compound types, and multiple languages typically take 4–8 weeks including QA and redirect testing.

More from our Blog