Last updated
A tenant migration or merger is planned and list data risks being dropped, renamed or reshuffled in the move, with no independent record of what it looked like before.
Two firms have merged, and the IT team has six weeks to move the acquired company’s SharePoint sites into the parent tenant. The migration tool will move the sites. What it will not do is leave behind a queryable record of what each list held before the move. Column types can be remapped on the way, lookups can break when a target list is recreated, and an item can go missing unnoticed. When the finance team asks in week eight whether every open purchase order came across, the only place to check is the source tenant, and by then it is half decommissioned.
Take a copy before anything moves
Replicate the lists into a database before the migration starts. The pre-migration state
then exists independently of whatever the migration tool does to the source tenant. Each
list becomes a table with its choice, person and lookup columns; managed metadata columns
are not replicated yet. Lookups are key columns, so the relationships between lists are
recorded in the copy even if the migration tool handles them differently. Deleted items
keep their row with sp_deleted_at set, so the snapshot records everything that existed
before the move.
| Title | Client lookup | Tags multi-choice | Owner person | Due |
|---|---|---|---|---|
| Riverside | Acme Ltd | Design; Build | Ada Lovelace | 2026-11-30 |
| Harbour | Borealis plc | Build | Alan Turing | 2027-02-14 |
then sync
- id
- title
- client_id → clients.id
- owner_id → sp_users.id
- due
- is_deleted soft delete
- sp_modified
- id
- title
- …
- project_id
- tag
One row per value: Riverside has two.
- id
- display_name
One users table shared by every list.
Prove the migration with a second copy
When the move is done, sign in to the destination tenant and replicate the same sites into a second set of tables, in another database or the same one under a different prefix. Now the two copies can be compared directly, row for row and column for column. The week-eight question becomes a query: purchase orders present before, missing after. That is a better answer than the migration tool’s own completion report. Keep the destination replication running afterwards as the ongoing sync.
A migration project often has access to only some sites in the destination tenant. SPSync
supports app permissions scoped per site with Sites.Selected, which matches that
arrangement.
Mergers of more than two tenants
For a merger of several companies, replicate each tenant’s sites into one database with a table prefix per site. The merged business gets a consolidated view of every list that none of the source tenants ever had, which is often the first report the new leadership asks for.
SPSync does not perform the tenant migration and needs no add-in installed in either tenant. For a project with a runbook, script one config per tenant with the command line and keep both in git, so the before and after copies can be rebuilt on demand.
Desktop, Azure or command line
All three editions run the same engine, write the same tables and use the same licence key, so the choice comes down to who looks after the sync.
Desktop app
For macOS and Windows. Sign in with Microsoft, tick the lists, choose a database and sync from your own machine on the schedule you set. Delegated sign-in needs no admin unless your tenant requires admin consent for apps.
In your Azure
SPSync in your Azure, a managed application for your own subscription, is deployed with you on a call while the Marketplace listing is prepared. Once deployed it is always on, so the sync keeps running with every laptop closed.
Command line
Every replication is a YAML file you can review and commit. Run it from a server, a container or a CI pipeline, and re-run it safely any time.
Set it up
Ready to run this against your own site? The setup guide takes you from install to a first replication, and the SharePoint connection guide covers signing in.
Script it
Everything you can click, you can script. The CLI, YAML config and automation below reproduce this scenario exactly. See the CLI reference.
spsync auth login --client-id 00000000-0000-0000-0000-000000000000
spsync export-config --output replication.yaml --pipeline-name tenant_migration_and_mergers --prefix tenant_migration_and_mergers --destination-kind mssql --connection-string-env SPSYNC_DB_CONNECTION --dataset-name tenant_migration_and_mergers --client-id 00000000-0000-0000-0000-000000000000 --site-url https://contoso.sharepoint.com/sites/example --all-lists
spsync doctor replication.yaml
spsync run replication.yaml pipeline_name: tenant_migration_and_mergers
prefix: tenant_migration_and_mergers
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: mssql
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: tenant_migration_and_mergers
scopes:
- site_url: https://contoso.sharepoint.com/sites/example
mode: entire_site
reconcile_interval_days: 7
create_reporting_views: false
Cron (SQL Server nightly):
# every night at 02:00
0 2 * * * cd /opt/spsync && SPSYNC_DB_CONNECTION="$(cat /etc/spsync/db.env)" uv run spsync run replication.yaml >> /var/log/spsync/run.log 2>&1 GitHub Actions:
# .github/workflows/spsync-run.yml
name: SPSync
on:
schedule:
- cron: "0 2 * * *"
workflow_dispatch: {}
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- run: uv sync
- run: uv run spsync run replication.yaml
env:
SPSYNC_DB_CONNECTION: ${{ secrets.SPSYNC_DB_CONNECTION }}
SPSYNC_LICENCE_KEY: ${{ secrets.SPSYNC_LICENCE_KEY }} Azure DevOps:
# azure-pipelines.yml
schedules:
- cron: "0 2 * * *"
branches: { include: [main] }
always: true
steps:
- script: uv sync
- script: uv run spsync run replication.yaml
env:
SPSYNC_DB_CONNECTION: $(SPSYNC_DB_CONNECTION)
SPSYNC_LICENCE_KEY: $(SPSYNC_LICENCE_KEY) Windows (PowerShell + Task Scheduler):
# Windows Task Scheduler action (see /docs/cli/automation/)
$env:SPSYNC_DB_CONNECTION = (Get-Content C:\spsync\db.env)
uv run spsync run C:\spsync\replication.yaml
schtasks /Create /SC DAILY /ST 02:00 /TN "SPSync" /TR "powershell -File C:\spsync\run.ps1" macOS (launchd):
# ~/Library/LaunchAgents/io.spsync.run.plist, then: launchctl load ~/Library/LaunchAgents/io.spsync.run.plist Secrets (licence key, connection strings) come from the environment, so never write them into the YAML. See scheduling and secrets.