Last updated
An operational dashboard needs to reflect what changed a few minutes ago, and querying SharePoint directly on a tight schedule is slow and gets throttled.
A screen on an operations floor showing open incidents by stage, pending approvals and the oldest unassigned item is only useful if it shows this morning’s queue. Refreshed overnight from SharePoint lists, it is stale by ten o’clock. Refreshed every minute straight from SharePoint, it gets throttled once a dozen people open the same dashboard, and each refresh still pays SharePoint’s cost of resolving lookups item by item.
One sync, many viewers
SPSync polls SharePoint once, on a schedule as short as a few minutes, and writes the
result to a database table. The dashboard queries that table as often as it likes.
SharePoint sees one well-behaved job that honours Retry-After and meters its own
resource use; the dashboard sees a fast SQL table, however many people are watching.
- 1ConnectSign in with Microsoft, or grant per-site app permissions.
- 2DiscoverPick sites, lists and libraries, or a whole site.
- 3MapSPSync reads each list's columns and creates the tables and keys.
- 4Full loadEvery item, paged the way Microsoft recommends, into the destination.
- 5Delta syncEvery few minutes only what changed moves: inserts, updates, deletes.
- 6ReconcileWeekly by default, or on demand with the command-line tool: a full comparison to confirm SharePoint and your database match, or to catch back up after an outage.
Steps 5 and 6 repeat for as long as the replication runs. Nothing is ever written to SharePoint.
How fresh the board will be
On the default five-minute schedule, the board is within a few minutes of SharePoint. That is close enough for an operations floor, and it is the accurate description: a schedule, with no real-time push. How short the schedule can usefully go depends on list size and on the tenant’s Graph throttling headroom. Below a couple of minutes, check that headroom first.
Counting by status without a lookup per row
The lists behind the board replicate as tables. When status or category is a lookup in
SharePoint, it is a key column in the database, so “open incidents by status” is a
GROUP BY over a join. Person columns resolve into the shared sp_users table, which
makes “assigned to me” views simple. An item removed from the queue keeps its row with
sp_deleted_at set, so it still counts in the closed totals and history does not shrink.
| 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.

Include the status and category lists when you pick the operational lists, so the joins resolve. Point the dashboard at the destination database and let it refresh on its own short interval. A board other people rely on should not depend on one person’s laptop, so run the sync from SPSync in your Azure or from the command line on a server.
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 kpi_dashboards --prefix kpi_dashboards --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name kpi_dashboards --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: kpi_dashboards
prefix: kpi_dashboards
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: postgres
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: kpi_dashboards
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.