Building web and mobile apps on data the business keeps in SharePoint

Build a web or mobile app on data the business keeps in SharePoint, with Supabase or Postgres tables that SPSync keeps current.

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

The data an app needs already lives in SharePoint lists, but building the app against SharePoint's API directly means paginated calls, opaque lookup IDs and no way to move fast.

A list that has served as a member directory for years holds the right data for a member-facing app: search the directory, see your own record, get told when an event is added. The SharePoint list API in front of it is the wrong tool for that app. Its paged calls were never meant for an app’s data layer, it returns lookup and person columns as IDs, and it offers no row-level security, generated API, realtime feed or ORM mapping.

Build on Postgres, edit in SharePoint

SPSync replicates the lists into Supabase or Postgres on a schedule and keeps the copy current. The app builds on a plain Postgres schema. Supabase’s generated APIs, row-level security policies and realtime subscriptions on table changes all work against the replicated tables. The membership team keeps editing in SharePoint, and the app sees each change within a few minutes on the default five-minute schedule.

The schema the app gets

Each list is a Postgres table with a stable primary key. A lookup is a key column with its display value beside it, so the app’s queries join related tables on it. Multi-choice columns are child tables. Person columns resolve into a shared sp_users table, which helps when the app shows who owns or created a record; multi-person values are still being confirmed on a live tenant. Deleted items keep their row with sp_deleted_at set, and the app decides whether removed members are shown.

What arrives in the database: one table per list, plus a users table.
What arrives in the database: one table per list, plus a users table.

Row-level security on replicated tables

“Members see only their own record” is a row-level security policy on the replicated table, written as for any other Supabase table, on top of the tables SPSync maintains. Include the lookup lists the data model joins against when you pick the lists, and set the schedule by how fresh the app needs to be.

When the app needs to write

SPSync only reads from SharePoint. An app that creates or edits records writes either to its own tables in Supabase or back to SharePoint through a separate integration. Many apps do both: profile preferences in the app’s own tables, directory data from the replica. A prototype can run its sync from the desktop app; once the app is in production, its data source should run independently of any one machine.

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