Skip to content

Step 3 — Create a migration task

Run the migration itself: filesystem ↔ library, list export, parallel sessions, and server-side packages. Part of Migration, which walks through the job model, incremental re-runs and the server-side five-step flow.

What File Notes
Filesystem → filesystem migrate_files.py Foundational MigrationJob
Export a list to JSON export_list.py "From SharePoint"
Library ↔ local (bidirectional) migrate_library.py --import, --concurrency
Files → library via a session migrate_session.py Parallel workers
Files → library server-side migrate_library_serverside.py Versions + ACLs
SPMT-style session migrate_spmt.py Register → task → start
Whole-site (same tenant) migrate_site.py migrate_all → one job per list; --preserve for best-effort timestamps/authorship/ACLs (web groups, web role assignments and each list's own ACLs, replayed after the content); also copies web navigation, each web's title/description (--no-site-settings) and its site pages — modern canvases and classic wiki bodies (--no-pages / --duplicate-pages) — and reports publishing pages, wiki markup and web-part bindings as a handoff
Build a package offline package_library.py Inspect the manifest, no tenant

Tuning a run

migrate_site.py reports the rate each list achieved, so a tuning change is judged on a number rather than a feeling:

python migrate_site.py --source-site https://contoso.sharepoint.com/sites/a \
  --target-site https://contoso.sharepoint.com/sites/b \
  --lists-in-flight 4 --concurrency 4 --batch-size 50 --pace 0.25 --stats 30 --apply
Flag What it changes
--lists-in-flight N lists migrated at the same time — the site-level knob: a site of many small lists is waiting on per-list round trips, so overlapping lists (not more workers per list) is what shortens the run
--concurrency N parallel workers per list — most help on many small files, none on a few large ones (the upload session is already the bottleneck)
--batch-size N items per $batch and how often progress/checkpoints flush
--pace SECONDS a fleet-wide floor between requests (RateLimiter), the proactive answer to a tenant that throttles
--stats N a heartbeat per active list (named, with its position in the run) instead of silence between Migrating… and the summary

--lists-in-flight is usually the bigger of the two parallelism knobs, and the cheapest to justify: each list keeps its own manifest and checkpoint, every worker runs on a context clone that shares the login and the connection pool, and --pace still bounds the whole run because the limiter is shared. The pilot shape — 24 lists, 6 site pages, a fresh target — is exactly the case it fixes.

Every run also prints how much the service pushed back: throttling: none (171 request(s)) or throttling: throttled 3/171 response(s) (429: 2, 503: 1), 42s of Retry-After. Raise --concurrency, and if throttling rises with the rate, the new number is the tenant's ceiling rather than the copy's speed — back off with --pace instead. --log-json carries the same counters as throttled events and in run.completed, so the curve can be charted from a collector.

See Throttling as a measured number.

What a run prints

A heartbeat names the list it belongs to and where that list sits in the run, and finishing a list prints its own line — so a watched run reads like a progress report instead of a stream of percentages:

Lists (24)
  library  Documents
  list     Tasks

Migrating into https://contoso.sharepoint.com/sites/b…
  Documents                   containers   4/24 (16.7%)   6.1 it/s   ETA 3s
  [  4/24] Documents          migrating   412/1,010 (40.8%)   9.7 it/s   ETA 1m02s
  ✓ [  4/24] Documents        1,010 copied   1m42s   9.9 it/s   2.4 MB/s
  ✓ [  5/24] Tasks            168 copied   12s   14.0 it/s
  ! [  6/24] Archive          212 copied   3 failed   2m04s   1.7 it/s

Warnings (3)
  ×2  content type 'Document': column 'Tags' was not linked: Access is denied
     list setting 'EnableMinorVersions' was not applied: Access is denied

Done in 12m31s — 2,104 copied, 12 skipped, 3 failed across 24 list(s)
  throttling: none (1,204 request(s))

✓ is a list that copied what it planned, ! one that left failures behind — re-run and the checkpoint retries only those. A warning that repeats is printed once with a count, because structure replay reports the same rejection once per list that hits it and a policy to read is not a line per list.