Skip to main content

divi-migrator

Migrate WordPress Avada / Plain HTML / Others? → Divi 5 Theme Framework. Vibe Coded (be warned!), built for AI-assisted workflows.

PyPI Python License CI

⚠️ STATUS: EXPERIMENTAL / PRE-RELEASE. This project is shared as-is for developers and AI agents working on Divi migrations. It is not a turnkey, certified product. Read Known Limitations and Disclaimer before use.

🛑 ALWAYS run against a staging copy first. Never on production. The tool rewrites and renames content; mistakes are your responsibility to recover from (rollback helpers exist, but are not guaranteed).


✨ Features

Feature Description
🔍 Auto-discovery Finds all pages/posts with builder content via WP REST API
🎯 Format detection Auto-detects Avada or plain HTML (Elementor detection reserved for a future release)
🎨 Design system Fetches global colors, fonts, presets from Divi Customizer
🔒 Safe replacement Existing content renamed to -old, moved to Draft (not deleted)
🏷️ Migration tracking Migrated posts get a divi-migrator category; all migrated items (pages + posts) carry a _divi_migrator provenance meta key, and every original is recorded in the audit trail
🔄 Resumable JSON checkpoint survives crashes/restarts
🧹 Deduplication Keeps newest draft per source, removes rest
✅ Verification Validates sections, rows, modules, images, buttons, colors
⚡ Rate limited Respects security plugins and shared-server resource limits
↩️ Rollback audit-rollback / rollback restore originals and delete migrated content (originals are never destroyed — renamed to -old)
🔌 Extensible Plugin architecture for new builders

🚀 Quick Start

# Install
pip install divi-migrator

# Configure (env vars or config.yaml)
export WP_URL="https://yoursite.com"
export WP_USER="migration-bot"
export WP_APP_PASSWORD="***"

# 1. Record current originals (pages/posts) so they can be tracked/rolled back.
#    Safe to re-run; migrate also auto-inits this on first use.
divi-migrate audit-init

# 2. Discover what can be migrated (lists only audit-recorded originals)
divi-migrate discover --type page
divi-migrate discover --type post

# 3. Dry run FIRST (migrate is dry-run by default — nothing is written)
divi-migrate migrate --type post --limit 5 --dry-run
divi-migrate migrate --type page --limit 50 --dry-run

# 4. Real migration — you MUST pass --execute to actually write
divi-migrate migrate --type page --limit 50 --execute
divi-migrate migrate --type post --limit 100 --execute

# 5. Cleanup duplicates (keeps newest draft per source)
divi-migrate cleanup --type post

# Inspect target site's design system
divi-migrate inspect

⚠️ migrate is dry-run by default. It only writes to the site when you pass --execute. Always do a --dry-run on staging and review before executing.


📋 Migration Process

Before running the migration, follow these steps to prepare your target site:

📌 Command order matters. Before discover/migrate can find anything, the originals must be recorded via divi-migrate audit-init (or the first migrate auto-inits it). See Quick Start for the exact command sequence. Everything below prepares the site; the CLI commands do the migration.

1. Create a Staging Website First

Never run migrations directly on production. Create a complete staging copy of your site.

2. Install and Activate the Latest Divi 5 Theme

Ensure the target site runs Divi 5 (not Divi 4).

3. Create a New Blank Homepage and Configure Design Foundation

  • Create a new homepage using Divi5
  • Configure Divi Options
  • Configure Divi Customizer settings
  • Set up global styling/design system (colors, fonts, spacing, button defaults, site background, section backgrounds - that will be reused on other pages)

Confirm the new design foundation is correct before migration. This is what all migrated content will use.

4. Use a WordPress User with an Application Password

The migrator authenticates via any WordPress user + Application Password (WP 5.6+). You can use your own admin account, but we recommend a dedicated migration user (e.g. migration-bot) so activity is isolated and easy to audit. It is not required — tracking and rollback do not depend on the username; they rely on the audit trail and the _divi_migrator provenance meta key.

  • Create the user (or reuse an existing one) and generate an Application Password.
  • Supply the username + password via WP_USER / WP_APP_PASSWORD (env) or config.yaml. If you use a different username than migration-bot, just set WP_USER to yours — nothing else needs to change.

📌 Author & identification. Migrated content is authored by the migration user (the one that authenticated), not the original author — all other original metadata (date, slug, categories, tags, featured image) is preserved. This is by design: once you're happy with the migration you can bulk-edit the author, or bulk-delete all migrated items with divi-migrate cleanup --author (defaults to the authenticated migration user's ID, so no manual ID lookup is needed). Pages and posts are both identifiable this way (WordPress authors both). The original author is kept only in the audit trail so rollback can restore it.

5. Install and Activate the DiviOps Plugin

The DiviOps plugin provides the REST API endpoints required for migration.

6. Connect Your AI Agent (optional)

This framework talks to WordPress directly via the WP REST API (using an Application Password) — no WordPress MCP server is required. The optional DiviOps plugin supplies additional Divi 5 REST endpoints used for best-effort block enrichment. An AI agent using this tool only needs network access to those REST endpoints; it does not need to drive a separate MCP server.

7. Give the AI Agent Migration Instructions

Example prompt:

"Migrate this website from [source theme] to Divi 5 using Divi-Migrator: https://github.com/chriscstewart/Divi-Migrator/."

The tool automatically tags/categorizes migrated content with divi-migrator so it can be easily reviewed, filtered, updated, or removed.


🏗️ Requirements

Component Version
WordPress 6.0+
Divi 5.0+
DiviOps Plugin Active (provides REST endpoints for Divi5)
Auth Application Password (WP 5.6+)

DiviOps Endpoints Required

Endpoint Method Purpose
/wp-json/diviops/v1/page/create POST Create draft with Divi content
/wp-json/diviops/v1/page/update-content/{id} POST Push Divi 5 blocks
/wp-json/diviops/v1/page/get/{id} GET Read page data
/wp-json/diviops/v1/page/get-layout/{id} GET Verify block tree
/wp-json/diviops/v1/meta/flush-cache POST Clear Divi cache
/wp-json/diviops/v1/validate/blocks POST Validate block structure
/wp-json/diviops/v1/global-color/list GET Fetch global colors
/wp-json/diviops/v1/global-font/list GET Fetch global fonts
/wp-json/diviops/v1/preset/list GET Fetch module presets

⚙️ Configuration

Environment Variables

# Required
WP_URL=https://yoursite.com
WP_USER=migration-bot
WP_APP_PASSWORD=***

# Optional
DIVIOPS_URL=https://yoursite.com          # if different from WP_URL
RATE_LIMIT=0.33                            # requests/sec for WP+DiviOps (0.33 ≈ 3s apart)
CHECKPOINT_DIR=./checkpoints
SSL_VERIFY=true

Config File (config.yaml)

wp:
  url: "https://yoursite.com"
  user: "migration-bot"
  app_password: "${WP_APP_PASSWORD}"
  ssl_verify: true
  timeout: 30

diviops:
  url: "https://yoursite.com"   # same as WP_URL if on same host

migration:
  rate_limit: 0.33          # requests/sec for WP+DiviOps (0.33 ≈ 3s apart; raise cautiously)
  batch_size: 50
  checkpoint_dir: "./checkpoints"
  max_retries: 5            # retry attempts on transient 5xx/429
  timeout: 30

verification:
  require_sections: true
  require_images: false
  require_buttons: false
  primary_color_token: "gcid-primary-color"
  # Expected primary color token in the Divi Customizer (fetched live at start).

design_system:
  fetch_on_start: true
  cache_ttl: 3600
  fallback_to_defaults: true
  background_color_token: "primary_background_color"

---

## 🔌 Extending for New Builders

Converters subclass `BaseConverter` and register an instance in the module-level
`registry`. The active converter is chosen automatically by `detect()`.

```python
# my_converters/custom.py
from divi_migrator.converters.base import BaseConverter, ExtractedAssets
from divi_migrator.converters.base import registry


class CustomBuilderConverter(BaseConverter):
    format_name = "custom_builder"

    def detect(self, content: str) -> bool:
        return "custom_builder_shortcode" in content

    def convert(
        self,
        content: str,
        source_id: int,
        design_system: object = None,
        wp_client: object = None,
    ) -> str:
        # Transform to Divi 5 block markup (see divi5_blocks helpers)
        return divi5_markup

    def extract_assets(self, content: str) -> ExtractedAssets:
        return ExtractedAssets()


# Register so discovery/migration can auto-select it
registry.register("custom_builder", CustomBuilderConverter)

There is no --converter CLI flag — divi-migrate discover/migrate auto-detect the format per item and pick the matching registered converter. To use your custom converter, ensure the module is imported (so registry.register runs) before invoking the CLI, e.g. via an entry point or by importing it in your own script that calls the library.

📚 Maintainer reference: the full Fusion-shortcode → Divi 5 module map (including which modules render natively vs. as semantic HTML inside a text block) lives in docs/FUSION_TO_DIVI_MAPPING.md.

📋 Changelog: see CHANGELOG.md for a per-release record of fixes, changes, and removals.


📁 Project Structure

divi_migrator/
├── cli.py              # Typer CLI entrypoint
├── config.py           # Pydantic Settings (env + YAML)
├── core/
│   ├── client.py       # WP REST + DiviOps clients
│   ├── inspector.py    # Design system fetcher
│   ├── discovery.py    # Content discovery
│   ├── orchestrator.py # Migration pipeline
│   ├── checkpoint.py   # JSON checkpoint I/O
│   └── verification.py # Layout verification
├── converters/
│   ├── base.py         # BaseConverter + Registry
│   ├── avada.py        # Fusion Builder → Divi 5
│   ├── elementor.py    # Elementor (stub — not implemented in this release)
│   └── plain_html.py   # HTML → Divi 5 wrapper
├── models/
│   ├── __init__.py     # MigrationMeta, CheckpointRecord, etc.
│   └── design_system.py # DesignSystem, GlobalColors, etc.

🧪 Testing

# Unit tests (no external services required)
pytest tests/unit/ -v

# Lint + type check (must match CI)
ruff check .
ruff format --check .
mypy --strict divi_migrator

# Offline end-to-end simulation + integrity scorecard (no live site needed)
python -m divi_migrator.cli simulate --items 8
# or: make simulate

Offline simulation (simulate)

divi-migrate simulate boots an in-memory WordPress + DiviOps harness and runs the full migration pipeline — convert → validate → schema check → create draft → verify → publish → rename original — without touching any real site. It then prints an integrity scorecard (drafts created, schema-valid drafts, originals renamed to -old, originals force-deleted = 0). This is the safe way to exercise the code start-to-finish and is the recommended smoke test for contributors and before any live run.

The harness lives in divi_migrator/testing/harness.py (FakeWPClient / FakeDiviOpsClient). It subclasses the real clients so it is a drop-in for the orchestrator's injectable wp / diviops / audit parameters, and every write is recorded for assertions. The orchestrator tests (tests/unit/test_orchestrator_new.py) use it to verify migration integrity, resume, and rollback offline.

Integration tests against a live WordPress + DiviOps site are not included in this release. CI runs the unit suite, ruff, and strict mypy only.


🔐 Security

  • No credentials stored in code — uses environment variables
  • Authentication details never logged
  • SSL verification enabled by default (warns if disabled)
  • URL validation before requests
  • Unsafe protocols rejected

⚠️ Known Limitations

Design-System Colors Are Fetched Live (Never Hardcoded)

The inspector resolves global colors from the live Divi Customizer, then backfills any gaps from the current Home page. There are no hardcoded hex fallbacks anywhere — if a color can't be fetched live it resolves to None and the relevant check is skipped rather than injecting a guess. The actual migrated content always carries its own source colors, so this only affects the optional verification/reference aid.

Section / Row / Module Backgrounds (DiviOps rendering)

The migrator emits correct, schema-valid Divi 5 background markup (e.g. background.color.desktop.value = "#770000" or background.image.desktop.value = {src, url}) when the source has a background, and omits the background key entirely when it does not (matches real Divi 5).

However, DiviOps's headless API does not apply section/row backgrounds at render time (verified 2026-08-08):

  • page/create / posts update-content drop module.decoration entirely.
  • module_update stores the attribute (visible via module_get, attr_count increases) but the published page / render_preview emit no background-color CSS.

This is a platform limitation of DiviOps on this host, not a code defect. The correct markup is preserved so backgrounds become editable in the Divi Visual Builder or self-heal if DiviOps ever renders them. If you need backgrounds applied immediately, set them manually per section/row in the Divi editor after migration.

Post & Page Creation Bypass DiviOps (by design)

Both posts and pages are created via the WordPress REST endpoints (posts / pages), not DiviOps:

  • create_content calls self.wp.post("posts"|"pages", ...) to create the draft (correct post-type, block markup stored in post_content). Divi 5 renders block markup directly from post_content, so this is the reliable creation path.
  • DiviOps update_page_content is attempted as a best-effort enrichment only (it may 500 on large payloads on shared hosting) and is not required for rendering.

Pages previously used DiviOps page/create, but that endpoint 500s on large payloads (shared hosting), leaving pages as empty placeholders. Routing page creation through WP REST fixed that. (See core/client.py::create_content.)


🤝 Contributing

Divi Migrator is open source and public — help is welcome, whether you're a WordPress developer who wants a safer Avada/Elementor → Divi 5 path, or you build AI agents and want a well-typed migration surface to wire into.

  • Read the guide: CONTRIBUTING.md — dev setup, the offline simulate harness, converter authoring, and the tag-driven release process.
  • File an issue: use the bug / feature / converter-support templates (Issues).
  • Send a PR against develop (or main); CI requires ruff + ruff format --check + mypy --strict + pytest to pass. The PR template lists the full checklist.
  • Good starting points: look for the good first issue / help wanted labels — e.g. expanding docs/FUSION_TO_DIVI_MAPPING.md, improving the Elementor converter, or adding golden-output snapshot tests.

Security issues go through private disclosure — see SECURITY.md, not public issues.


📄 License

MIT License - see LICENSE


🛡️ Disclaimer / No Warranty

This software is provided "AS IS" and "AS AVAILABLE", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement.

The authors and contributors take NO RESPONSIBILITY and assume NO LIABILITY for any damage, data loss, broken layouts, SEO regression, or any other consequence arising from the use of this tool. This includes, without limitation:

  • Content that is renamed, rewritten, unpublished, or left in a draft state.
  • Lost styling, backgrounds, or module fidelity after migration (see Known Limitations).
  • Failures due to hosting limits, plugin conflicts, rate limiting, or DiviOps behaviour on your specific site.
  • Any outcome of running the tool on a site that is not a disposable staging copy.

You are solely responsible for:

  1. Backing up your site (database + uploads) before running anything.
  2. Testing on staging first, verifying the result, and only then considering production.
  3. Reviewing migrated content in the Divi Visual Builder before publishing.

By using this software you acknowledge and accept these terms. If you do not agree, do not use the software.

Release files for divi-migrator 0.1.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for divi-migrator 0.1.4
File Size Uploaded
divi_migrator-0.1.4.tar.gz 82.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for divi-migrator 0.1.4
File Interpreter ABI Platform
divi_migrator-0.1.4-py3-none-any.whl Python 3 none any Details

Total release size: 174.8 kB

Release files / divi_migrator-0.1.4.tar.gz

Download URL divi_migrator-0.1.4.tar.gz
Size 82.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3dd129b7c8a77aa977b6c60f02c7747b3b931c5d789b141fe277cd4cd843e530
BLAKE2b-256 checksum
How to use checksums
432784d1b317a2d6edc12fec9948283475eebbbb194ecddce806af319461eb36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release files / divi_migrator-0.1.4-py3-none-any.whl

Download URL divi_migrator-0.1.4-py3-none-any.whl
Size 92.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c0498c41180b64737af9af732571abec73b82b2733704bee7a06079806a1b461
BLAKE2b-256 checksum
How to use checksums
32acb8320030ee4b7e5dae770f80401f26d7105ec9affc751e1945f7478bf6d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page