Last updated
SharePoint lists cannot be a dbt source directly, so the data either stays out of the model layer or goes through a brittle, hand-maintained loading step first.
dbt can model anything that sits in a database it connects to, and SharePoint lists do
not. Project codes, cost centres and approval status kept in SharePoint have no path to a
sources.yml entry. Either that data stays out of the model layer, or someone writes a
loading script and dbt is asked to trust a table that changes shape whenever the script
does.
A source dbt can declare
SPSync replicates the lists into the database or warehouse dbt already uses, with a schema
that behaves as a source table should. Column names follow the list, delta sync on a
schedule keeps the tables current, and no dbt run ever touches SharePoint. Declare the
tables in sources.yml, give them a freshness check tied to the sync schedule, and build
staging models on top.
| 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.
What staging models get to work with
Each list is a table. A lookup column is a key column pointing at another replicated
table, so a staging model joins two source() tables on a key and never resolves an ID by
hand. Multi-value columns arrive as child tables, one row per value, ready for an
intermediate model to aggregate. Deleted items keep their row with sp_deleted_at set,
which lets a model tell “removed” from “never existed”. If a flatter shape is easier to
start from, SPSync’s generated reporting views can sit under a staging model.
Schema changes without broken models
Someone adds a column, someone removes one, someone turns a text column into a number.
New columns appear on the next run and removed columns stay, so a model that selects them
keeps compiling. On SQL Server, PostgreSQL, MySQL, ClickHouse, Oracle, SQLite and DuckDB,
a retyped column writes to a new _v2 column and the type under an existing model stays
put.
Running dbt after the sync
The usual arrangement is the command line: run SPSync as the step before dbt run, in
whatever already orchestrates the project, whether cron, Airflow, dbt Cloud jobs or CI. A
model that needs the latest data runs after each sync; one that needs a weekly baseline
can run after the reconcile, weekly by default. Large lists are supported, so SharePoint
sources are not limited to small reference tables, and SPSync never writes to SharePoint,
so the project carries no risk to the lists it reads.
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 dbt_on_sharepoint --prefix dbt_on_sharepoint --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name dbt_on_sharepoint --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: dbt_on_sharepoint
prefix: dbt_on_sharepoint
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: postgres
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: dbt_on_sharepoint
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.