Last updated
The list an integration needs sits in SharePoint, and every integration ends up calling the Graph API on its own schedule, hitting the same throttling and lookup problems.
When three systems need the same SharePoint list, the usual result is three integrations polling Graph on three schedules. Each handles throttling its own way, each receives lookup columns as IDs, and each breaks separately when the list changes. An approved supplier list kept in SharePoint so buyers can edit it is a typical case: the ERP needs it so purchase orders go to approved suppliers, the CRM team wants it too, and an ERP job’s paging can fail the week the list passes 5,000 items.
One copy for every system that reads the list
SPSync replicates the lists an integration depends on into tables in your database and refreshes them on a schedule. The ERP job, the CRM connector and any stored procedure read SQL. SPSync is the only thing still talking to Graph, so a third or fourth reader adds no load on SharePoint.
Tables an integration can join
Each list is a table with its calculated, person, choice and lookup columns; managed
metadata columns are not replicated yet. A lookup is a key column into the table for the
target list, so a purchase order line joins to its supplier like any two SQL tables. A
multi-choice column becomes a child table with one row per value, so a stored procedure
never parses a delimited string; multi-value lookups follow the same pattern once checked
against a live tenant. People fields resolve into one
sp_users table shared by every replicated list.
| 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.
Deleted items stay in the table with sp_deleted_at set. An integration that reconciles
against its previous run keeps the evidence that a supplier existed and was removed.
Getting the integration onto the replica
Grant only what the integration needs. Sign in with delegated access to the sites it
reads, or have an admin grant SPSync Sites.Selected app permissions to just those
sites, and pick the lists themselves; the whole site is rarely needed. Point SPSync at SQL
Server, Azure SQL or Postgres and it creates and maintains the schema. Then change the
integration’s job or connector to read the replicated tables.
For a small integration owned by one group, the desktop app is often enough. When the replication should sit next to the integration code, the command line keeps its config in the same repository, reviewed in the same pull requests.
When the list changes shape
A buyer adds a column and it appears in the table on the next run. A column whose type
changes moves into its own _v2 column on SQL Server, PostgreSQL, MySQL, ClickHouse,
Oracle, SQLite and DuckDB, so the integration’s next run reads what it read yesterday.
Other destinations do not fork a retyped column today. Large lists are supported, and
SPSync never writes to SharePoint or installs anything there.
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 erp_crm_integration --prefix erp_crm_integration --destination-kind mssql --connection-string-env SPSYNC_DB_CONNECTION --dataset-name erp_crm_integration --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: erp_crm_integration
prefix: erp_crm_integration
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: mssql
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: erp_crm_integration
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.