MODX Evolution to MODX Revolution Site Migration
MODX Evolution and Revolution are fundamentally different systems. Evolution: procedural PHP approach, no OOP, no namespace, API via global $modx object. Revolution: PSR-compliant architecture, xPDO ORM, components (Extras). No direct upgrade path — full content migration required.
Current Evolution Site Analysis
-- Document counts by templates
SELECT t.templatename, COUNT(d.id) as count
FROM site_content d
JOIN site_templates t ON t.id = d.template
GROUP BY t.templatename;
-- TV parameters
SELECT tv.caption, tv.type, COUNT(tvv.id) as used
FROM site_tmplvars tv
LEFT JOIN site_tmplvar_contentvalues tvv ON tv.id = tvv.tmplvarid
GROUP BY tv.id;
-- Custom snippets
SELECT name, LENGTH(snippet) as size FROM site_snippets;
-- Plugins
SELECT name, plugincode FROM site_plugins WHERE disabled = 0;
Content Export from Evolution
// Export script to JSON (run on Evolution server)
$db = new PDO('mysql:host=localhost;dbname=modx_evo;charset=utf8', 'user', 'pass');
$resources = $db->query("
SELECT d.*, t.templatename
FROM site_content d
JOIN site_templates t ON t.id = d.template
WHERE d.deleted = 0
ORDER BY d.parent, d.menuindex
")->fetchAll(PDO::FETCH_ASSOC);
foreach ($resources as &$resource) {
// Get TV values
$tvs = $db->prepare("
SELECT tv.name, tvv.value
FROM site_tmplvar_contentvalues tvv
JOIN site_tmplvars tv ON tv.id = tvv.tmplvarid
WHERE tvv.contentid = ?
");
$tvs->execute([$resource['id']]);
$resource['tvs'] = $tvs->fetchAll(PDO::FETCH_KEY_PAIR);
}
file_put_contents('content-export.json', json_encode($resources, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
Import to MODX Revolution
// Import script (run on Revolution server after installation)
require_once 'config.core.php';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('web');
$data = json_decode(file_get_contents('content-export.json'), true);
// Map old templates to new
$templateMap = [
'default_template' => 1,
'inner_page' => 2,
'catalog' => 3,
'product' => 4,
];
// Map old parent IDs to new (after structure change)
$parentMap = [];
foreach ($data as $evo_resource) {
$resource = $modx->newObject('modDocument');
$resource->fromArray([
'pagetitle' => $evo_resource['pagetitle'],
'longtitle' => $evo_resource['longtitle'],
'description' => $evo_resource['description'],
'alias' => $evo_resource['alias'],
'introtext' => $evo_resource['introtext'],
'content' => migrateContent($evo_resource['content']),
'published' => $evo_resource['published'],
'publishedon' => $evo_resource['publishedon'],
'createdon' => $evo_resource['createdon'],
'template' => $templateMap[$evo_resource['templatename']] ?? 1,
'parent' => $parentMap[$evo_resource['parent']] ?? 0,
'menuindex' => $evo_resource['menuindex'],
]);
if ($resource->save()) {
$parentMap[$evo_resource['id']] = $resource->id;
// Migrate TVs
foreach ($evo_resource['tvs'] as $tvName => $tvValue) {
$resource->setTVValue($tvName, $tvValue);
}
}
}
// Content migration function (Evolution tags → Revolution)
function migrateContent(string $content): string {
// Evolution uses [*alias*] for links, Revolution uses [[~id]]
// Replacement requires manual mapping or aliases
// Remove outdated Evolution tags
$content = preg_replace('/\[(\*|\+|\!)[^\]]+\]/', '', $content);
return $content;
}
Snippets and Plugins Migration
Snippets Evolution are written in procedural style with global $modx. Revolution API is different:
// Evolution (old API)
$docs = $modx->getDocuments($parents = [5], $published = 1);
// Revolution (correct API)
$c = $modx->newQuery('modResource');
$c->where(['parent' => 5, 'published' => 1]);
$docs = $modx->getCollection('modResource', $c);
Snippets need rewriting. For typical tasks (output lists, menus, forms) — use ready Revolution Extras: pdoResources, pdoMenu, FormIt.
Image Migration
# Image directory structure is identical (assets/images/)
rsync -avz old-server:/var/www/evo-site/assets/images/ \
/var/www/revo-site/assets/images/
HTML content paths may need replacement if directory structure changed.
Timeline
| Site Type | Duration |
|---|---|
| Simple (up to 50 resources, standard snippets) | 1–2 weeks |
| Medium (100–500 resources, custom snippets) | 3–5 weeks |
| Large (1000+ resources, complex functionality) | 2–3 months |







