SharePoint to Azure Database for PostgreSQL

SharePoint lists and library metadata in Azure Database for PostgreSQL Flexible Server, in the subscription and VNet your apps already use.

Book a call

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.

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:

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:

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:

TableHolds
fm_requestssp_item_id, title, status, building_id, requested_by_user_id, sp_created, sp_modified, sp_deleted_at
fm_buildingsThe Buildings list, keyed on sp_item_id
fm_requests__tradesOne row per request and trade: sp_item_id, value, ordinal
sp_usersThe 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_request
from fm_requests r
join fm_buildings b on b.sp_item_id = r.building_id
join fm_requests__trades t on t.sp_item_id = r.sp_item_id
where r.sp_deleted_at is null
and r.status <> 'Closed'
group by b.title, t.value
order 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

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:

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

  1. Sign in with delegated access, or ask an admin to grant Sites.Selected on the sites the sync needs so it can run unattended.
  2. Store the connection string, with sslmode=require and port 5432, in an environment variable.
  3. Open the network path: a firewall rule or a host inside the VNet.
  4. Choose lists, libraries or a whole site and start the first run, which builds the schema and loads every item.
  5. 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.

Book a call

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.