Last updated
One way: SharePoint stays the system of record, and SPSync keeps a read-only copy of your lists current in Azure Database for PostgreSQL.
Status: Shipped · product: SPSync for Relational Databases
If your SharePoint tenant and your applications both live in Azure, Azure Database for PostgreSQL keeps the copy of your list data in the same subscription, resource group and network as the apps that read it. Azure Monitor, automated backups and the scaling controls you already use apply to the replicated tables too.
How do I connect SPSync to Azure Database for PostgreSQL?
Flexible Server requires TLS by default: the require_secure_transport server parameter
is on when a server is created. Adding sslmode=require to the connection string is
enough, because the server certificate chains to a public root and no extra certificate
file is needed. If your security standard asks for sslmode=verify-full, the trust
store on the sync host needs the root certificates Microsoft lists in
TLS and SSL for Azure Database for PostgreSQL,
and no intermediate certificates, since Microsoft rotates those. Keep the whole
connection string in an environment variable; the configuration file holds only the
variable’s name.
SPSync authenticates with a Postgres username and password. Entra ID token authentication is not supported yet. The user needs rights to create tables in the schema SPSync writes to.
Networking is set in the Azure portal, not in SPSync:
- Public access. Add a firewall rule for the address the sync runs from.
- Private access. Run the sync from inside the VNet, or from a network peered with it.
SPSync uses its standard PostgreSQL destination for Flexible Server. The automated tests
run against stock PostgreSQL, and the Azure behaviour described here comes from
Microsoft Learn, so connect once with psql from the sync host to confirm the firewall
rule or the VNet route before the first run.
A login for the sync
The administrator you named when you created the server belongs to azure_pg_admin
and is not a true superuser, but it can create roles and schemas. Use it once to set up
a login for the sync, and keep the administrator’s password out of the sync host
altogether. The sync writes its tables to the schema named in the replication’s
dataset_name, and uses a second schema with _staging on the end to hold each run’s
changed rows before it merges them:
create role fm_sync login password 'from-key-vault';create schema facilities authorization fm_sync;create schema facilities_staging authorization fm_sync;If the password comes from Key Vault, a start-up script on the sync host can fetch it and set the environment variable before the sync starts.
PgBouncer, connections and storage on Flexible Server
Three Flexible Server behaviours affect a SharePoint replication, each described on Microsoft Learn:
- Built-in PgBouncer. Turning on
pgbouncer.enabledstarts PgBouncer on port 6432 of the same host, in transaction pooling mode by default. It is not available on the Burstable tier. Point the sync at port 5432 and leave 6432 to apps that open many short connections, as Microsoft’s PgBouncer article describes. - Connection limits. The default
max_connectionsfollows the compute size, and a few slots are reserved. On the smallest Burstable size, B1ms, that leaves 35 user connections out of 50, shared by your app, your BI tool and the sync, per Limits in Azure Database for PostgreSQL. - Storage turns read-only when full. When usage reaches 95% of the provisioned storage, or free space drops below 5 GiB, the server switches to read-only and every write fails, the sync’s included. Storage can be grown but not reduced, so turn on storage autogrow or watch the metric, especially before a first load of a large site.
The schema it writes
A facilities request list on a site replicated with the prefix fm, with a Building
lookup, a Requested by person column, a Status choice and a multi-choice Trades
column, becomes fm_requests, keyed on the SharePoint item ID. Replicate the Buildings
list as well and the run produces:
| Table | Holds |
|---|---|
fm_requests | sp_item_id, title, status, building_id, requested_by_user_id, sp_created, sp_modified, sp_deleted_at |
fm_buildings | The Buildings list, keyed on sp_item_id |
fm_requests__trades | One row per request and trade: sp_item_id, value, ordinal |
sp_users | The people table every list shares |
Identifiers follow Postgres rules, lowercased and capped at 63 bytes, and SPSync shortens
and disambiguates long SharePoint names to fit. A column added in SharePoint appears on
the next run. Removed ones stay. A type change SPSync cannot convert safely goes into a
new _v2 column.
Worked example: open requests by building and trade
select b.title as building, t.value as trade, count(*) as open_requests, min(r.sp_created) as oldest_requestfrom fm_requests rjoin fm_buildings b on b.sp_item_id = r.building_idjoin fm_requests__trades t on t.sp_item_id = r.sp_item_idwhere r.sp_deleted_at is null and r.status <> 'Closed'group by b.title, t.valueorder by oldest_request;A facilities manager reads that as a backlog: which building has the oldest open
plumbing job, and which trades are queuing up. Turn on reporting views in the
replication and SPSync also creates v_fm_requests, which adds the requester’s display
name and leaves out deleted rows, so a Power BI model can import the view without
writing the join.
Watching it in Azure Monitor
Two Flexible Server metrics are worth an alert once the sync is running. Storage percent
warns you long before the read-only threshold described above. Active connections shows
the sync’s connection appear for each run and disappear between them; a line that never
drops back means something else is holding connections open. For the data itself,
select max(sp_synced_at) from facilities.fm_requests shows when the sync last wrote a
row. It stays still when nobody edits the list, so read it next to the run history in
SPSync, which records every run whether or not items changed.
When the first connection fails
- A timeout. The firewall has no rule for the sync host’s outbound address, or the host has no route into the VNet on a private-access server.
no pg_hba.conf entry for host ... no encryption. The server requires TLS and the string is missingsslmode=require.password authentication failed. The variable on the sync host still holds a password you have since changed, or the user name is missing the role you created.permission denied for database. The sync’s login cannot create its schemas. Create both as the administrator and make the login their owner.
Who reads it
A Power BI model can use import or DirectQuery against the tables. An App Service or Functions app uses them as an application read replica. Analysts write joins across lists, and several sites can share the server with a prefix per site. dbt models often follow once the tables are stable.
Vectors alongside the tables
Flexible Server offers pgvector as an allow-listed extension. Add VECTOR to the
azure.extensions server parameter, run create extension vector, and switch the
replication to the Postgres with pgvector destination. Each
row then carries an embedding of the columns you choose, from Azure OpenAI, OpenAI or a
local model, refreshed only when that text changes. For search with ranking and filters
built in, Azure AI Search is the other destination
that receives embeddings from SPSync.
Can Fabric mirror the replicated tables?
Yes, with conditions worth knowing before you plan on it. Microsoft Fabric can mirror a Flexible Server database into OneLake continuously, so the SharePoint tables can reach a Fabric lakehouse without a second sync. Microsoft’s mirroring limitations page sets the boundaries that matter here:
- No Burstable source. Mirroring needs a General Purpose or Memory Optimized server.
- A table cap. Up to 1,000 tables can be mirrored, and a whole-site replication counts every child table.
- Column changes on a mirrored table are not supported. SPSync adds a column when
someone adds one to the list, and forks a retyped column into
_v2, so check the mirror’s status after a SharePoint schema change. - Some types are skipped.
jsonbis on the unsupported list, so a location column’s_jsonpart does not mirror, while its latitude and longitude columns do.
The mirroring overview covers set-up. SPSync’s own Fabric destination is on the roadmap, not shipped; until it is, Flexible Server plus mirroring is a route to OneLake you can set up today, within the limits above.
Maintenance, failover and the sync
Flexible Server applies updates in a maintenance window you can choose, and a zone-redundant server fails over to its standby when the primary has trouble. The server name in the connection string stays the same through both. A run in progress when the server restarts fails, and the next scheduled run reads every change since the last one that finished, so the window costs a few minutes of freshness and no data. If dashboards read from a read replica, Flexible Server’s virtual endpoints give the reader a name that keeps pointing at a replica after a promotion, which saves editing the report’s connection.
An agent over the Flexible Server copy
An agent built in Azure can query the tables with a Postgres MCP server pointed at the
server, or at a read replica
so its queries stay off the primary the sync writes to. Give it a Postgres login with
SELECT on the fm_ tables and no other grant. SPSync ships no agent, but it has a
read-only MCP server of its own, spsync mcp serve, if you would rather not run another.
SharePoint item permissions are not copied, so a request raised on a restricted site is
an ordinary row here, and the login’s grants plus row-level security are what scope the
agent. The agent never touches Graph, so it adds no load to the tenant however many
questions it asks. For a Foundry agent, our post on
Azure AI Foundry agents and SharePoint list rows
sets out what each Foundry tool reads and connects Microsoft’s SQL MCP Server to a view
on the replica.
Which compute tier for a sync?
A Burstable server is enough for the sync itself on a few lists, and the cheapest way to try it. It rules out three things this page has covered: the built-in PgBouncer, Fabric mirroring and a generous connection count. General Purpose is the usual tier once Power BI, an app and an agent all read the same tables. Scaling between tiers restarts the server, so do it between runs.
Power BI reaches a server on public access directly. For a server on private access, the Power BI service needs a data gateway with a route into the VNet, the same arrangement as for any other private Azure database.
From sign-in to first rows
- Sign in with delegated access, or ask an admin to grant
Sites.Selectedon the sites the sync needs so it can run unattended. - Store the connection string, with
sslmode=requireand port 5432, in an environment variable. - Open the network path: a firewall rule or a host inside the VNet.
- Choose lists, libraries or a whole site and start the first run, which builds the schema and loads every item.
- Later runs read only changes, with a reconcile, weekly by default.
Flexible Server compute and storage are billed by Microsoft, separately from SPSync. The first full load is the heaviest work the server sees from the sync; later runs move only the items that changed.
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.
Frequently asked questions
Public access or a private endpoint, which does SPSync need?
Either works. With public access, add the sync host's outbound address to the server's firewall rules. With a private endpoint, run the sync from a machine inside the VNet or one peered with it.
Can I use Entra ID authentication instead of a Postgres password?
No, Microsoft Entra ID token authentication is not wired up yet. SPSync signs in with a Postgres username and password over TLS.
Should the sync connect through the built-in PgBouncer on port 6432?
Use port 5432 for the sync. It connects once per run and creates or alters tables, and pooling adds nothing to that. Keep port 6432 for apps that open many short connections.
Flexible Server or Single Server, does it matter?
SPSync connects to both the same way. Microsoft has retired Single Server, so use Flexible Server for anything new.
Talk to us about Azure Database for PostgreSQL
SPSync is licensed per installation and per target family, with Standard Support included. Tell us which targets you need and how many machines, and we will size it with you. See how licensing works.
Set it up
Sign in to SharePoint with the SharePoint connection guide, then follow the PostgreSQL setup guide to create the database user and open the connection for Azure Database for PostgreSQL. If the connection test fails, the troubleshooting guide lists the errors people hit and what fixes them.
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 azure_database_for_postgresql --prefix azure_database_for_postgresql --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name azure_database_for_postgresql --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: azure_database_for_postgresql
prefix: azure_database_for_postgresql
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: postgres
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: azure_database_for_postgresql
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.