Continuous backup and history of lists

A queryable copy of each SharePoint list outside SharePoint, refreshed every few minutes, with deleted items kept on record.

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

SharePoint's own backup and restore options are all-or-nothing, slow to restore from, and do not give a queryable copy of what a list looked like on a given day.

SharePoint’s recycle bin can bring back deleted list items, provided someone notices within its retention window and knows which items to restore. It cannot tell you what the list held last Thursday. Clean up an equipment bookings list in a hurry, remove a few hundred items that looked old, and some of them turn out to be next month’s bookings. For a list the business runs on, whether incidents, approvals, inventory or bookings, a copy outside SharePoint is the difference between a query and a guess.

An independent copy that keeps what was removed

SPSync replicates the list into a database table and refreshes it on a schedule, so a current copy always exists outside SharePoint. Soft delete is the default: when an item is removed from the list, it stays in the table with sp_deleted_at set. Over time the backup holds everything the list has contained since the first sync.

The first run is a full load and each run after it is a delta. A reconcile, weekly by default, checks the complete state, so you can confirm the copy still matches the list, or catch it up after an outage on your own server or in Microsoft 365.

Recovering the deleted bookings

The deleted bookings are rows with sp_deleted_at set, so finding them is one query: rows deleted since the clean-up began. Every replicated column is there. Lookups are key columns and multi-value columns are child tables, so related data comes back with them, and person fields resolve into the shared sp_users table. Putting the items back into SharePoint is a manual step today; SPSync’s job is to make sure the data to do it still exists.

Choosing the schedule

A shorter interval leaves a smaller gap between an item being created and it being in the backup. Every few minutes fits a busy list; hourly fits a slow one. Keep the replication running all the time, as a backup and not a one-off job. Postgres or SQL Server work well, and DuckDB gives a lightweight local copy. Large lists are handled the same way as small ones.

A backup should not stop when a laptop closes, so SPSync in your Azure is usually the right home for it, or the command line scheduled from cron, Task Scheduler or CI with its config committed. Hard delete exists in the command-line tool for a database that should mirror SharePoint’s current state, but for a backup the default is what you want. SPSync never writes to SharePoint, so running it puts the source list at no risk.

Once the backup holds every item, it can also let you trim the live list. The post on how to archive old SharePoint list items walks through deleting closed items with a scheduled flow while their rows stay in the database.

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