divi-migrator
WordPress Avada / Plain HTML → Divi 5 migration framework (Elementor planned, not yet implemented). Built for AI-assisted workflows, zero vendor lock-in.
✨ 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 | All migrated content tagged with divi-migrator category |
| 🔄 Resumable | JSON checkpoint survives crashes/restarts |
| 🧹 Deduplication | Keeps newest draft per source, removes rest |
| ✅ Verification | Validates sections, rows, modules, images, buttons, colors |
| ⚡ Rate limiting | Configurable, respects security plugins |
| 🔌 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="***"
# Discover what can be migrated
divi-migrate discover --type page
divi-migrate discover --type post
# Dry run
divi-migrate migrate --type post --limit 5 --dry-run
# Real migration (resumable!)
divi-migrate migrate --type page --limit 50
divi-migrate migrate --type post --limit 100
# Cleanup duplicates
divi-migrate cleanup --type post
# Inspect target site's design system
divi-migrate inspect
📋 Migration Process
Before running the migration, follow these steps to prepare your target site:
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. Create a Dedicated WordPress Migration User
- Create a separate account only for migration tasks
- Generate an Application Password for this user
- This isolates migration activity and simplifies rollback/auditing
5. Install and Activate the DiviOps Plugin
The DiviOps plugin provides the REST API endpoints required for migration.
6. Connect Your AI Agent to Required MCP Tools
- DiviOps MCP — required for all Divi 5 operations
- WordPress MCP — for additional WP operations: WordPress REST API endpoints used by default, but some agents may prefer this MCP.
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=2.0 # seconds between requests
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: 2.0
batch_size: 50
checkpoint_dir: "./checkpoints"
max_retries: 3
timeout: 30
verification:
require_sections: true
require_images: false
require_buttons: false
primary_color_token: "gcid-primary-color"
default_section_bg: "#f7f4ef"
design_system:
fetch_on_start: true
cache_ttl: 3600
fallback_to_defaults: true
primary_color_override: null
section_bg_override: null
🔌 Extending for New Builders
# my_converters/custom.py
from divi_migrator.converters import BaseConverter, register_converter
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=None) -> str:
# Transform to Divi 5 shortcodes
return divi_shortcodes
def extract_assets(self, content: str) -> ExtractedAssets:
return ExtractedAssets()
# Register
from divi_migrator.converters import register_converter
register_converter("custom_builder", CustomBuilderConverter)
Then use: divi-migrate migrate --converter custom_builder
📁 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 v0.1.0)
│ └── plain_html.py # HTML → Divi 5 wrapper
├── models/
│ ├── __init__.py # MigrationMeta, CheckpointRecord, etc.
│ └── design_system.py # DesignSystem, GlobalColors, etc.
🧪 Testing
# Unit tests
pytest tests/unit/ -v
# Integration (requires test WP site)
pytest tests/integration/ --wp-url=https://test.site --wp-user=... --wp-pass=...
# Lint + type check
ruff check .
mypy divi_migrator/
🔐 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
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/ postsupdate-contentdropmodule.decorationentirely.module_updatestores the attribute (visible viamodule_get,attr_countincreases) but the published page /render_previewemit nobackground-colorCSS.
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_contentcallsself.wp.post("posts"|"pages", ...)to create the draft (correct post-type, block markup stored inpost_content). Divi 5 renders block markup directly frompost_content, so this is the reliable creation path.- DiviOps
update_page_contentis 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.)
📄 License
MIT License - see LICENSE
Metadata
Release files for divi-migrator 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| divi_migrator-0.1.0.tar.gz | 69.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| divi_migrator-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 151.4 kB
Release files / divi_migrator-0.1.0.tar.gz
| Download URL | divi_migrator-0.1.0.tar.gz |
|---|---|
| Size | 69.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
701d6054a5f7872b3bdde3ce7b23fefac8aa0e5968de277b22d6322fd5b058c1
|
|
BLAKE2b-256 checksum How to use checksums |
9511599b5f22c901dde10eb7c99b77d5a3b908ff304089d910e82be18cf486d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.
Transparency logRelease files / divi_migrator-0.1.0-py3-none-any.whl
| Download URL | divi_migrator-0.1.0-py3-none-any.whl |
|---|---|
| Size | 81.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e2c9a9e68ca05ddd1a5588c1f693d699e854f34916a96a55d1facb72ea27870e
|
|
BLAKE2b-256 checksum How to use checksums |
562b1d26762243cc028b22405f914407720857805a8c89cbec0ccccf56afeada
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.
Transparency log