Last updated
Most SharePoint replication tools are click-through wizards: every environment is set up by hand again, nothing is reviewable, and nothing runs unattended from CI.
Replicating the same twelve lists in a test tenant, then staging, then production, is three rounds of the same forty clicks in a wizard. Nothing can be reviewed before a run, no scheduler or CI pipeline can drive it without wrapping the GUI, and once the person who clicked moves on there is no record of what was chosen.
The whole replication in one file
SPSync works from the command line as well as the desktop. spsync export-config
discovers the sites and lists you point it at and writes a ReplicationConfig YAML file.
That one plain-text file describes the entire replication and means the same thing every
time it runs, in every environment.
The file is safe to commit. Connection strings and the licence key are environment
variable references, such as connection_string_env: SPSYNC_DB_CONNECTION, never
literal secrets. Once it is in git, a change to the replication is reviewed in a pull
request: a teammate sees which sites, lists and destination it touches before it runs
anywhere.
Running it from CI
spsync doctor checks the ODBC driver, the Graph permissions and the destination
connection before spsync run moves any data. A broken pipeline fails early with a clear
message, before a half-finished sync can happen. The first run against a config is a full
load and every run after it is a delta, so a retried CI job or a rescheduled trigger never
writes a row twice.
Many sites at once
spsync export-config accepts --site-url more than once, and
spsync batch create batch.yaml writes one config per site across a whole tenant’s worth
of sites in a single command. Twelve lists in three tenants is three files, one per
tenant, each reviewed once.
Scripting needs no separate edition: the command line works from the first command. The
desktop app and SPSync in your Azure run the same engine behind a picker, which some
people prefer day to day. This use case is the one the command line was built for:
spsync run against a committed YAML file, safe to re-run from CI.
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 scriptable_migrations --prefix scriptable_migrations --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name scriptable_migrations --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: scriptable_migrations
prefix: scriptable_migrations
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: postgres
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: scriptable_migrations
scopes:
- site_url: https://contoso.sharepoint.com/sites/example
mode: entire_site
reconcile_interval_days: 7
create_reporting_views: false
Cron (PostgreSQL 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.