Passer for Craft CMS

Configuration

Plans, not settings

Passer is configured per migration plan, in the control panel under Passer. A plan holds the source connection, the mapping and the phase selection. It is saved, re-runnable and exportable, because a real migration is never run once: it is run against a copy, examined, corrected, and run again over weeks while the WordPress site is still being edited.

The config file

Plugin-wide defaults can be set under Settings → Passer, or in config/passer.php so they can differ per environment:

<?php
return [
    // Records per batch. Lower it on constrained hosts.
    'batchSize' => 50,

    // Seconds before a request to the source gives up
    'requestTimeout' => 30,

    // Download attachments into a volume, and the largest file to accept
    'downloadMedia' => true,
    'maxAssetSizeMb' => 128,

    // Rewrite links to the old site as Craft reference tags, and expand shortcodes
    'rewriteUrls' => true,
    'expandShortcodes' => true,

    // Keep going past a record that fails, and list it in the report
    'continueOnError' => true,

    // Where the finished report is emailed
    'reportEmail' => 'migrations@example.com',
];

Settings only the config file can set

Two settings are never shown in the control panel or saved from it, because they decide what the server will connect to:

<?php
return [
    // Let media downloads and the REST source reach private addresses — a WordPress on the
    // same LAN, in Docker, or on localhost. Off by default.
    'allowPrivateNetwork' => true,

    // Source settings the connect form doesn't offer, applied whenever that kind of source
    // is used and never stored in a plan.
    'sourceOverrides' => [
        'wpcli' => ['sshOptions' => 'ProxyJump=bastion.example.com ConnectTimeout=10'],
        'database' => ['sslCa' => '/etc/ssl/certs/rds-ca.pem'],
    ],
];

sshOptions accepts connection settings — ProxyJump, ConnectTimeout, ServerAliveInterval, Compression, IdentitiesOnly and the like — as Key=Value pairs. Options that run a command, such as ProxyCommand or LocalCommand, are refused wherever they come from.

Choosing destinations

During plan, each domain gets a destination. Passer detects what is installed and offers the preferred target, falling back to something that needs nothing but Craft:

DomainPreferredFallback
SEOSEOmatic, ether/seoPlain Craft fields Passer creates
RedirectsRetour, Venveo Redirect, FriendsCraft's own config/redirects.php (Craft 5.6+)
MenusFreeNav, Verbb NavigationA structure section of link entries
FormsFormieBandage, or a written specification
CommentsVerbb CommentsCounted and reported
Widgets—Global sets, or a widgets section
CommerceCraft CommerceProducts as plain entries

Mapping

The plan proposes a full mapping you can override row by row:

  • which post type becomes which section and entry type
  • where each meta key lands, or that it is deliberately dropped
  • which taxonomy becomes which category group
  • which ACF field group becomes which field layout

The proposal comes out of analyse, so it is shaped by what is actually in the database rather than by what WordPress claims is installed.

The wp-import bridge

If craftcms/wp-import is installed, Passer reuses its ACF adapters and Gutenberg block transformers for anything it does not handle itself — including adapters you wrote in config/wp-import/.

The coupling is deliberately loose. wp-import ships as dev-main with no tagged releases, so a bridge that cannot load leaves Passer's own adapters in place rather than failing the migration.

More in Development

Pairs well with Passer

For the developer on the project. Migrations in and between sites, a code editor field, schema docs, content inventories, reports and email testing.