SharePoint to Neon

Serverless Postgres for SharePoint list data: replicate into a Neon branch, wake from scale-to-zero on each run, and test changes on a copy.

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 Neon.

Status: Shipped · product: SPSync for Relational Databases

Getting SharePoint list data into Neon usually starts as a script that calls Graph on a timer, and it breaks the first time a list passes 5,000 items or the compute is asleep when the script connects. SPSync takes that job over. It writes each list into a Neon branch as normalised Postgres tables and, after the first load, moves only the items that changed.

Neon separates storage from compute, so a database can sleep when no one queries it and be copied in seconds as a branch. Both properties change how you run a SharePoint replication. The sync wakes the database on its schedule, and a branch gives you a throwaway copy of the replicated tables for testing.

How do I connect SPSync to a Neon branch?

Each Neon branch has its own connection string, shown in the Connect dialog of the Neon console. Neon offers two forms of it: a direct string, and a pooled one whose host carries a -pooler suffix. Give SPSync the direct string. Neon’s pooler runs PgBouncer in transaction mode, which drops session settings between transactions, and Neon’s own advice is a direct connection for tools that create and alter tables. A sync does both on its first run and again whenever someone adds a column to a list. The pooled string stays useful for the app that reads the tables.

Paste the string unchanged. Some strings carry an options=endpoint%3D... parameter that older client stacks need, and trimming it can break the connection. Neon requires TLS, so keep sslmode=require on the end. The whole string, password included, belongs in an environment variable on the machine that runs the sync; the replication’s YAML file holds only the variable’s name.

SPSync signs in with the Neon role and password in that string. Create a role for the sync and give your app a separate role for reading. The sync’s role needs two schemas it owns: the one named by the replication’s dataset_name, where the tables live, and a second with _staging on the end, where each run holds changed rows before merging them. One Neon database can then hold the SharePoint copy beside your app’s own tables without the two mixing.

SPSync talks to Neon through the same PostgreSQL destination it uses for a server you host yourself. Its automated tests run against stock PostgreSQL, and the Neon details on this page come from Neon’s documentation, so make your first run against a branch before you aim it at the one your reports read.

Scale to zero and the sync schedule

Neon suspends a compute after five minutes without activity and resumes it on the next connection, usually within a few hundred milliseconds. The sync’s retries absorb that pause, but avoid a very short connect timeout in the string. A run every five minutes keeps compute awake for most of the working day; a fifteen-minute or hourly interval lets it sleep between runs, which is cheaper on a usage-billed plan.

Two kinds of run take longer than the rest. The first load reads every item in every list you picked, and the reconcile, weekly by default and configurable per replication, compares the whole list with the table to catch anything a missed run or an outage left behind. Schedule a large first load for an evening if a dashboard shares the compute.

Branches for safe changes

A branch starts as a copy-on-write clone of its parent, so making one costs nothing until its data starts to differ. Two ways to use one:

A branch is a separate connection target. It stays current only if a replication writes to it, so a branch you test on drifts from SharePoint once you stop syncing it. Neon can reset a branch from its parent when you want a fresh copy, and a branch given an expiry date removes itself when the test is over.

Neon limits that shape the setup

Worked example: a sales pipeline list on Neon

A sales site, replicated with the table prefix crm, has an Opportunities list with these columns: Title, Account (a lookup to the site’s Accounts list), Owner (a person column), Stage (a choice column), Amount (a number), Close date and Products (a multi-choice column). Pick both lists in the replication and the first run creates:

TableWhat it holds
crm_opportunitiesOne row per opportunity, keyed on sp_item_id: title, stage, amount as double precision, close_date as timestamptz, account_id, owner_user_id, and the sp_ columns such as sp_modified and sp_deleted_at
crm_accountsThe Accounts list, keyed on sp_item_id, which account_id points at
crm_opportunities__productsOne row per product ticked: sp_item_id, value, ordinal
sp_usersEveryone named in a person column: id, login, display_name, email

There are no foreign key constraints between them. The key columns are there to join on, and you can add constraints yourself on a branch to see whether your app needs them. Open pipeline by product and owner, counting live rows only, reads like this:

select p.value as product,
u.display_name as owner,
count(*) as opportunities,
sum(o.amount) as pipeline
from crm_opportunities o
join crm_opportunities__products p on p.sp_item_id = o.sp_item_id
left join sp_users u on u.id = o.owner_user_id
where o.sp_deleted_at is null
and o.stage not in ('Won', 'Lost')
group by p.value, u.display_name
order by pipeline desc;

SharePoint cannot answer that in one view: the product split needs the multi-choice column split into rows, and the owner name needs a join that the list’s own API makes you do in code. Try the query on a branch first if you plan to turn it into a view that a dashboard reads.

Can an AI agent query the Neon copy?

Yes, through a Postgres MCP server you run and point at the copy under a read-only role, or run spsync mcp serve, SPSync’s own read-only MCP server. On Neon, a sensible home for that connection is a read replica on the same branch: it is a second compute over the same storage, so the agent’s queries never compete with the sync’s writes and no data is duplicated. Give the agent its own role:

create role pipeline_agent login password 'choose-a-long-one';
grant usage on schema crm to pipeline_agent;
grant select on all tables in schema crm to pipeline_agent;
alter default privileges for role spsync_sync in schema crm
grant select on tables to pipeline_agent;

Here spsync_sync is the role in the sync’s connection string. The last statement covers tables the sync creates later, such as a child table for a new multi-choice column. SharePoint item permissions are not copied into Neon, so this role, plus row-level security if you need it, is what limits what the agent can read. The agent never holds a Graph permission, and its questions cost SharePoint nothing.

Where Neon fits

Developers use a branch as the database behind a preview build of an app that reads SharePoint data. Report authors point a Power BI model at the main branch. Analysts join lists in SQL, and a multi-site consolidation can be tried on a branch before it is run for real.

A first run against Neon

  1. Create the sync’s role and a schema for it on a test branch.
  2. Use delegated sign-in for a first look at SharePoint; for scheduled runs, an admin grants the app Sites.Selected on the sites involved.
  3. Store the branch’s direct connection string in an environment variable.
  4. Choose lists, libraries or a whole site. The first run builds the tables and loads every item; later runs read changes only.

If the first connection fails with a message that the endpoint ID is not specified, the client stack on the sync host lacks SNI support and needs the options=endpoint%3D... parameter from Neon’s own string. A password error after a role reset in the console means the environment variable still holds the old password.

Neon bills compute time and storage per project, separately from SPSync.

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

Should SPSync use Neon's pooled connection string or the direct one?

The direct one, without `-pooler` in the host. Neon's pooler runs PgBouncer in transaction mode, and Neon points tools that create or alter tables at a direct connection. Your app can keep using the pooled string to read the same tables.

Can I use a Neon branch to test a change before it reaches production?

Yes. Create a branch, point a second SPSync replication at it, and try a new configuration or a downstream schema change there. The branch your reports read is untouched.

Does SPSync work with scale-to-zero?

Yes. If Neon has suspended the compute, the sync's connection wakes it, as any client would, and the run carries on once the compute is back. Avoid a very short connect timeout in the connection string so the first attempt is not cut off.

Talk to us about Neon

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 Neon. 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 neon --prefix neon --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name neon --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: neon
prefix: neon
auth:
  mode: delegated
  client_id: 00000000-0000-0000-0000-000000000000
  tenant_id: common
destination:
  kind: postgres
  connection_string_env: SPSYNC_DB_CONNECTION
  dataset_name: neon
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.