Point-in-time archive with soft deletes for audit

Keep an audit record of SharePoint lists in a database you control, where deleted items stay on file with the time they were removed.

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

Someone deletes or edits a SharePoint list item and there is no record of what it looked like beforehand, which is a problem the moment an auditor asks.

An auditor sampling purchase approvals asks about one that is no longer in the list. Someone deleted it while tidying up, and the evidence that it was ever approved went with it. SharePoint’s version history helps with edits, but it is hard to query in bulk and it goes when the item is deleted outright. An audit record has to survive the delete button.

A record that keeps deleted items

SPSync replicates the list into a database table you control, and soft delete is the default. When an item is removed from SharePoint, its row stays in the table with sp_deleted_at set to the time the deletion was seen. The approval the auditor asked about would still be there, with every column it had on the day it was removed.

Each sync brings the table up to date with the list, so an edited field shows its new value after the next run. A reconcile, weekly by default, compares the whole list end to end, so you can confirm the archive matches SharePoint, or catch it up after an outage on your own server or in Microsoft 365.

What auditors can query

The lookup, choice and person columns come across, so the archive can be queried in full; managed metadata columns are not replicated yet. Lookups are key columns and multi-value columns are child tables. Auditors who do not want to write joins can be given a generated reporting view per list. Hard delete exists in the command-line tool for a database that should mirror SharePoint’s current state, but for an archive the soft-delete default is what you want.

Where the archive sits, and who can read it

The archive is a database you run, with your own access rules and backup regime, independent of SharePoint’s retention settings. Restrict it to the people who need it and give auditors read access to the tables or the views. SPSync never writes to SharePoint, so archiving cannot alter the record being archived. Running the sync from SPSync in your Azure keeps it capturing changes outside office hours, and the command-line config can be kept under review in a pull request as part of a documented compliance process.

What it does not replace

SharePoint’s version history is not replicated. The table holds each item’s current values and every deleted item, which answers “what existed, and when was it removed”. For “what did this field say before Tuesday’s edit”, SharePoint’s own versioning is still the place to look.

The same soft-delete default is what makes it safe to shrink a list that has grown too big to use. The post on how to archive old SharePoint list items covers trimming closed items from SharePoint once the database has them, and compares that with Purview retention labels and an archive list.

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_archive_compliance --prefix sharepoint_archive_compliance --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name sharepoint_archive_compliance --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_archive_compliance
prefix: sharepoint_archive_compliance
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_archive_compliance
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.