P
Content Migration Bundle

Pimcore Developer Tool

Deploy Pimcore content like code.

Convert Pimcore Documents, Assets and Data Objects into versioned Doctrine migrations for reproducible deployments.

Terminal
bin/console pimcore:content-migration:generate
PHPGenerated migration preview
DocumentBuilder::create('/news/article')
    ->setTitle('Example Article')
    ->setPublished(true)
    ->save();

Without migrations

Content sync is manual and error-prone.

SQL dumps overwrite data. IDs differ between local, staging, and production. There is no diff, no rollback, no PR review for content.

SQL dump conflicts

mysqldump exports full tables. Importing overwrites existing records and auto-increment values.

ID mismatch

document_id=42 in dev points to a different document in staging. Relations break silently.

Prod-to-dev direction

Editors change production directly. Those changes are never committed and get lost on next deploy.

No Git history

Content lives in the database only. git log shows nothing. git bisect cannot find regressions.

How it works

bin/console generates PHP migrations.

Select Documents, Assets, or Data Objects in Pimcore. Run the console command. Get a PHP migration class that uses a fluent builder API.

  • Generates AbstractMigration subclasses in src/Migrations/
  • Builder API: DocumentBuilder::create('/path')->setTitle('...')->save()
  • Handles relations via path or reference, not hardcoded IDs
  • Runs with bin/console doctrine:migrations:migrate
  • Compatible with Pimcore 10.x and 11.x
  • Output is readable PHP, not serialized data
Version20240315_ContentMigration.php
// Generated by Content Migration Bundle

public function up(Schema $schema): void
{
    DocumentBuilder::create('/news/article')
        ->setTitle('Example Article')
        ->setPublished(true)
        ->save();
}

Demo

Generate content migrations from the Pimcore backend.

Select content in Pimcore, run the generator command, and commit the resulting PHP migration.

terminal
$bin/console content:migration:create [TYPE] [ID] [--namespace=...] [--with-children] [--inline-wysiwyg]

Documents

$bin/console content:migration:create document 142
Pimcore document editor screenshot
PHPCreates or updates the document /de/News including all dependencies
# Create page /de/News
        $builder = \PimcoreContentMigration\Builder\Document\Personalization\PageBuilder::findOrCreate('/de/News');
        $builder->setController('App\Controller\NewsController::listingAction');
        $builder->setContentMainDocument($documentEnNews);
        $builder->setProperty('footer', 'document', $documentDeSharedIncludesFooter, true, true);
        $builder->setProperty('language', 'text', 'de', true, true);
        $builder->setProperty('newsletter_confirm_mail', 'document', $documentDeMailsNewsletterConfirm, true, true);
        $builder->setEditable(\PimcoreContentMigration\Builder\Document\EditableBuilder::createInput('headline', 'Neuigkeiten aus der Oldie-WElt'));
        $builder->setIndex(2);
        $builder->setType('page');
        $builder->setPublished(true);
        $builder->save();

Assets

$bin/console content:migration:create asset 68
Pimcore asset management screenshot
PHPCreates or updates the asset /Car Images/ac cars/automotive-car-classic-149813.jpg including all dependencies
# Create image /Car Images/ac cars/automotive-car-classic-149813.jpg
        $builder = \PimcoreContentMigration\Builder\Asset\ImageBuilder::findOrCreate('/Car Images/ac cars/automotive-car-classic-149813.jpg', __DIR__ . '/files/asset/Car Images/ac cars/automotive-car-classic-149813.jpg');
        $builder->setType('image');
        $builder->setMimeType('image/jpeg');
        $builder->setCustomSetting('imageWidth', 1280);
        $builder->setCustomSetting('imageHeight', 720);
        $builder->addMetadata('authorLink', 'input', 'https://www.pexels.com/@flickr', '');
        $builder->addMetadata('license', 'input', 'CC0', '');
        $builder->save();

Data Objects

$bin/console content:migration:create object 372
Pimcore data object editor screenshot
PHPCreates or updates the object /Product Data/Accessories/lights/head lights/ac cars-cobra 427-head lights including all dependencies
# Create object /Product Data/Accessories/lights/head lights/ac cars-cobra 427-head lights
        $builder = \PimcoreContentMigration\Builder\DataObject\ConcreteBuilder::findOrCreate('/Product Data/Accessories/lights/head lights/ac cars-cobra 427-head lights', \App\Model\Product\AccessoryPart::class);
        $builder->setPublished(true);
        $builder->set('manufacturer', $objectProductDataManufacturerACCars);
        $builder->set('series', $objectProductDataCarsAcCarsCobra427);
        $builder->set('mainCategory', $objectProductDataCategoriesProductsSparePartsLightsHeadLamps);
        $builder->set('erpNumber', '2176611183');
        $builder->set('categoryCode', '394');
        $builder->set('owner', 'PI');
        $builder->save();

Result

Content in version control.

Idempotent migrations

Same migration runs on local, staging, prod. No environment-specific patches.

PR review for content

Content changes appear in git diff. Review before merge. Revert with git revert.

CI/CD integration

doctrine:migrations:migrate in your deployment pipeline. Content deploys with code.

Path-based references

Relations use /news/article not id=42. Works across databases with different auto-increment states.

No sync debugging

Stop comparing databases manually. Migration either runs or fails with a stack trace.

960 € / year

Roughly 80 € per month. One deployment debugging session costs more than that. Agency license covers unlimited projects and developers.

Pricing

Pimcore Content Migration Bundle

One plan for agencies building and maintaining Pimcore projects.

Agency License

960/ year

960 € per year = about 80 € per month

≈ 1–2 developer hours

  • Unlimited projects
  • Unlimited developers
  • No domain limits
  • No license key
  • 4 week free trial
  • Updates included

FAQ

Technical details.