🦊 MigrationFox Docs

SharePoint Site Migration

Cross-tenant site-collection migration covering content types, list views, version history, and modern Site Pages. Runs in three sequential phases on top of the base document-library copy.

This is not SPMT

MigrationFox does not preserve original authors or Modified/Created timestamps on migrated items. That behaviour is on the roadmap but not shipped. Items land with the service principal as author and the migration run time as their timestamps. If you need full date/author preservation today, run SPMT for the file body and use MigrationFox to carry the site scaffolding (content types, views, pages, permissions) alongside it.

The site connector uses the credentials from the SharePoint / OneDrive setup (Entra ID application with Sites.ReadWrite.All + Files.ReadWrite.All). No extra scopes are needed for Phases 1 and 3. Phase 2 web-part rewriting uses only the same Graph endpoints.

Phase 1Content Types

Before any list or library is created at the destination, the connector enumerates site-level content types at the source and recreates the custom ones at the destination. Parent content types (e.g. Item, Document) are referenced by ID rather than re-created, so the hierarchy lines up.

What gets copied

What does not

Graph endpoint used: GET /sites/{id}/contentTypes?$expand=columnLinks then POST /sites/{id}/contentTypes at the destination.

Phase 2List Views

After each list is created and items are in place, the connector copies the custom views for that list. View creation is best-effort via the Graph Pages API — enough for the common view shapes (filter on one or two columns, a sort, a column set, a row limit) but not a full CAML round-trip.

What works

What does not round-trip cleanly

Audit views post-migration

The migration report flags every view where the destination Graph response did not include the full source filter text. For lists where views matter (e.g. an HR onboarding tracker), open the flagged views in the destination and verify the filter before handing the site off to users.

Phase 3Version History

Graph does not expose a way to write historical versions of a list item with their original authors or timestamps. To avoid silently dropping that history, MigrationFox materializes it as JSON snapshots in a hidden long-text column on each item.

How it works

  1. On each destination list that has any versioned item in the source, the connector provisions a hidden column called _VersionHistory of type Note (long text, plain — no rich text). The column is hidden from views but remains query-able via the item's field collection.
  2. For every item with more than one version at the source, the connector pulls all historical versions via GET /sites/{id}/lists/{listId}/items/{itemId}/versions.
  3. The versions are serialized as a JSON array — each element containing the version label, the modifier's UPN at the source, the source timestamp, and the full field-value map at that revision — and written into _VersionHistory on the destination item.

Reading the history back

The hidden column is readable with any standard SharePoint client. A representative PowerShell snippet:

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/hr" -Interactive
$item = Get-PnPListItem -List "Employees" -Id 42
$history = $item["_VersionHistory"] | ConvertFrom-Json
$history | Format-Table versionLabel, modifiedBy, modifiedAt

If you later add a proper history-preserving API to the destination (Microsoft does not currently have one), the JSON payload is rich enough to replay every version in order.

Long-text column limits

A SharePoint Note column allows up to ~63,999 characters. Items with extremely deep version histories (thousands of revisions on a wide row) can overflow; in that case the connector writes the most recent 500 versions and logs a warning on the item. Overflow is rare outside of automation-driven lists.

Site Pages (Modern)

Modern Site Pages — the canvas/web-part page model that replaced wiki pages — do not round-trip through the generic list-item migration because their content lives in canvasLayout plus web-part JSON rather than simple field values. They run through a separate Graph Pages API path.

What copies

Manual fix needed: cross-tenant web-part references

Many web parts embed absolute URLs pointing back at the source tenant — a Highlighted Content web part that queries "files in contoso.sharepoint.com/sites/marketing", a News web part pinned to a source-tenant news site, a YouTube/Stream embed that references an internal Stream URL. The connector carries the exact JSON across, so those web parts will still render against the source tenant until they are repointed. This is always a manual step. The migration report lists every page that contains a web-part property matching the source host name so you can review them in order.

Recommended workflow

Migrate documents and lists first so the destination has real content. Run Site Pages last. After the run, open each flagged page in edit mode, repoint the Highlighted Content / News / Stream web parts to the destination equivalents, and republish. The pages already look correct structurally at that point — you are only fixing the query targets.

Permissions & Cross-Tenant UPN Mapping

On a same-tenant migration, permissions copy as-is — the source principals resolve against the destination directory because it is the same directory. On a cross-tenant migration (the common case for mergers and divestitures) users have different UPNs on the two sides and you need a mapping.

CSV format

The permission-mapping CSV uses the same two-column shape as the bulk mail migration CSV:

sourceEmail,destEmail
jane.doe@source-tenant.com,jane.doe@dest-tenant.com
raj.patel@source-tenant.com,raj.patel@dest-tenant.com
ext-vendor@gmail.com,vendor.contractor@dest-tenant.com

Rules carry over identically: optional header, both addresses validated, one mapping per row, no quoting. Addresses not present in the CSV are dropped from the permission set on the destination — the migration report lists every source principal that had no mapping so you can decide whether to re-run with a richer CSV or leave them removed.

What gets mapped

What does not

Running a Site Migration

  1. In your project, add SharePoint as both source and destination with an app-only credential on each side. See SharePoint / OneDrive setup.
  2. Create a new job with Site migration as the job type. Pick source site URL and destination site URL (both must exist — the connector does not create the destination site collection).
  3. Upload the permission-mapping CSV if this is a cross-tenant migration.
  4. The phases run automatically in order: content types → lists with items → list views → version history snapshots → Site Pages → permissions. You can watch each phase tick off in the dashboard.

Related