Last updated
Status: Shipped · product: SPSync for Warehouses & Lakes
The analytics team queries everything from Athena, and the one source missing from the bucket is the SharePoint list that tracks supplier onboarding. Someone exports it to Excel at month end, converts it, and uploads a file that is out of date by the next morning. SPSync replaces that chore: it reads the list through Microsoft Graph on a schedule and writes each table to your bucket as Parquet after every run. The copy goes one way, and SharePoint stays the system of record.
How do I get a SharePoint list into an S3 bucket?
S3 is a target of the Parquet export that comes with the DuckDB destination. SPSync keeps a DuckDB copy of the lists on the sync host, merges each run’s changes into it, and then uploads every table to the bucket. You set it in the Destination screen of the desktop app or server (the Parquet export fields), in the command-line tool’s YAML config, or through an AI assistant using the admin MCP server, which sends you to a secure page for the keys.
Two other destinations also write to S3, if you would rather have a table format than plain files:
- Delta Lake merges changes into Delta tables on
s3://bucket/prefix, with a transaction log and time travel. - Apache Iceberg writes through a REST catalog whose warehouse can sit on S3.
The rest of this page covers the Parquet route. The Parquet page covers the same export for a local folder.
Bucket, region and credentials
The bucket is a URL of the form s3://<bucket>/<prefix>. Everything SPSync writes goes
under that prefix, so one bucket can hold several replications side by side.
Credentials come from one of two places:
- an access key ID and secret access key, plus a session token for temporary credentials, entered in the Destination screen or held in environment variables whose names go in the config;
- the standard AWS credential chain, when you leave those out. That covers the usual environment variables, a shared credentials file, and an EC2 instance or ECS task role.
Set the region when the bucket is outside the default region your AWS configuration
resolves. For MinIO or another S3-compatible store, also set the endpoint URL. The
identity needs to list the bucket and write objects under the prefix; spsync doctor
lists the bucket to check the credentials before the first run.
SPSync’s automated tests run this upload against MinIO; it has not been run against a live AWS account, so try one table first.
Table folders and file names
Each table gets its own folder under the prefix, and each run writes two objects into it:
<prefix>/<table>/<run_id>.parquet, a full snapshot for that run, never changed afterwards;<prefix>/<table>/current.parquet, the latest snapshot, replaced every run.
The run ID is a random identifier, so sort history files by their last-modified time rather than by name.
A supplier onboarding list with a Category lookup, an Owner person column and a
multi-choice Documents received column produces supplier_onboarding/,
supplier_onboarding__documents_received/ and the shared sp_users/. The lookup arrives
as category_id and category_value, the person as owner_user_id, and the
multi-choice column as one row per item and value in its own folder. An item deleted in
SharePoint keeps its row, with sp_deleted_at filled in.
New SharePoint columns appear in the next run’s files, and older history files keep the
columns they had. When a column changes type, the files get a _v2 column alongside the
original, the same as the DuckDB copy behind them.
Can Athena and Redshift Spectrum query the files?
Yes, with one thing to watch. An external table in Athena or Redshift Spectrum points at a
folder and reads every file in it, so a table over supplier_onboarding/ returns the
current snapshot and every history file together. Both engines expose the file each row
came from: Athena as the "$path" column
(Athena SELECT reference) and
Redshift Spectrum as the $path pseudocolumn
(Redshift Spectrum external tables).
Filter on paths ending /current.parquet for the latest state, and on the history files
when you want to compare runs.
DuckDB, Spark and Trino can read s3://<bucket>/<prefix>/<table>/current.parquet as a
single file, which avoids the question altogether. DuckDB does it with its httpfs
extension, so an analyst on a laptop can join supplier_onboarding to sp_users in a
few lines of SQL.
Steps
- Sign in with delegated access, or have an admin grant
Sites.Selectedon the sites you replicate. - Create the bucket, or pick a prefix in an existing one, and an identity that can list it and write objects.
- Write a
replication.yamlwith a DuckDB destination, the bucket URL, the region and the names of the environment variables that hold any keys. - Choose the lists, run
spsync doctor, then run the first replication. It loads every item and writes the first snapshot. - Point Athena, Redshift Spectrum, DuckDB or Spark at the table folders.
Every run adds one history file per table, so a short schedule over many tables adds up.
An S3 Lifecycle expiration rule deletes objects a set number of days after they were
created, and replacing an object resets that date
(lifecycle rules).
Every run replaces current.parquet, so a rule like that removes old history files and
leaves the current snapshot alone. In a bucket with versioning switched on, each run
also leaves the previous current.parquet as a noncurrent version, so add a rule for
those too. Storage, requests and egress are billed by AWS, and access is whatever the
bucket policy already enforces.
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
Is Amazon S3 its own destination in SPSync?
Not as a separate type. S3 is a bucket option on the DuckDB destination's Parquet export, set in the Destination screen or the command-line tool's YAML config. The Delta Lake destination also writes to S3 directly, and the Iceberg destination can use a warehouse on S3 through a REST catalog.
Which credentials does SPSync use for the bucket?
An access key pair, with an optional session token: typed into the Destination screen, or read from environment variables you name in the command-line tool's config. Leave them out and SPSync uses the standard AWS credential chain: environment variables, a shared credentials file, or the role of the EC2 instance or ECS task it runs on.
Does it work with MinIO or another S3-compatible store?
Yes. Set the endpoint URL alongside the bucket. SPSync's automated tests run the upload against MinIO.
Why does Athena return the same item more than once?
A table's folder holds current.parquet and one history file per run, and an Athena table over the folder reads all of them. Filter on "$path" to keep only the rows from current.parquet, or use the history files on purpose to see past runs.
Is Google Cloud Storage supported?
No, it is not supported today. The Parquet export writes to a local folder, Amazon S3 and S3-compatible stores, and Azure Blob Storage.
What happens to items deleted in SharePoint?
The row stays and its sp_deleted_at column is set, so a report can show deleted items or filter them out. To remove the row instead, set deletes: hard on that replication in the command-line tool's YAML config. Runs from the desktop app and from SPSync in your Azure always keep the row.
Talk to us about Amazon S3 (Parquet)
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 DuckDB and the Parquet export setup guide to create the database user and open the connection for Amazon S3 (Parquet). 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.
# This destination is available now. `spsync export-config` does not take it as a
# --destination-kind value yet, so write replication.yaml by hand (see the YAML tab)
# and run it with `spsync run replication.yaml` like any other destination. pipeline_name: amazon_s3
prefix: amazon_s3
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: duckdb
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: amazon_s3
parquet_export_bucket_url: s3://your-bucket/spsync
parquet_export_s3_region: eu-west-2
# Leave these out to use the standard AWS credential chain instead.
parquet_export_s3_access_key_id_env: AWS_ACCESS_KEY_ID
parquet_export_s3_secret_access_key_env: AWS_SECRET_ACCESS_KEY
scopes:
- site_url: https://contoso.sharepoint.com/sites/example
mode: entire_site
reconcile_interval_days: 7
create_reporting_views: false
Cron (this destination 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.