Part of: Databases and apps on SharePoint data

SharePoint lists in PostgreSQL, ready for Metabase and apps

Tags: Relational databasesDesktopCommand line

SPSync copies SharePoint lists into PostgreSQL on a schedule. Each lookup arrives as a key column, each multi-value column as a child table, and deleted items stay in the table with a flag. Metabase, an internal app or an AI agent then queries plain tables with SQL, and because every table is written in one transaction, a report never reads a half-finished load.

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.

Northfield Retail Group decides its product range in SharePoint and measures it in PostgreSQL. Buyers keep Product Range, Store Audits and Suppliers as lists because that is where the store teams edit them; the sales volumes those decisions depend on sit in a database. This walkthrough copies the three lists into PostgreSQL with SPSync, so a Metabase dashboard, an internal stock-check app and an AI agent can all read one set of tables.

Building the dashboard straight on the lists is where it goes wrong. Once a list passes 5,000 items, a query that filters or sorts on a non-indexed column can fail, Metabase refreshes slow to minutes, and the supplier on each product comes back as an id. A Power Automate flow copies rows until someone changes a lookup or a multi-value column. A hand-built loader works, but its schema has to be edited every time the range team adds a column.

What changes when Metabase reads a replica

SharePoint’s list APIs serve the SharePoint pages well and a dashboard that pulls a whole list on a timer badly. SPSync reads the lists once in full, then follows them with delta queries, and writes the result to PostgreSQL tables on a schedule. Metabase and the app talk to PostgreSQL only. SPSync creates the schema from the lists: lookups and people become key columns, multi-value columns become child tables, and every run after the first fetches only what changed. The SharePoint to PostgreSQL page has the destination detail.

Signing in with the store ops account

Install the desktop app (or SPSync in your Azure for a tenant-wide setup) and sign in with the account that already opens Product Range, Store Audits and Suppliers. The sign-in is delegated, so SPSync can see those lists and nothing the account cannot. Before an admin approves Sites.Selected, the permissions wizard sets out what it grants and why, and SPSync never writes to SharePoint. On the command line the sign-in is a device code, and the token is cached in the operating system’s keychain.

Choosing the lists and the PostgreSQL connection

Tick the three lists, name the replication and pick PostgreSQL. The destination takes a standard connection string, so any PostgreSQL server you can reach will do. Preview the schema first to see each column, its type and which columns are lookups. With reporting views switched on, each list also gets a view that drops soft-deleted rows and shows people by name.

How the retail lists look as tables

Each list becomes a table keyed on the SharePoint item id, which tells every later run which row to update. The supplier lookup on Product Range becomes supplier_id, pointing at sp_suppliers. Checklist Areas, a multi-value choice on Store Audits, becomes the child table sp_store_audits__checklist_areas with one row per area per audit, so no query has to split a delimited string. Every person on every list resolves to one row in sp_users, so a supplier account manager and a store inspector join the same way.

The schema follows SharePoint as it changes. A column added in SharePoint appears in the table on the next run, and a removed column stays where it is, so a dashboard question that uses it still works the following morning. If a column’s type changes completely, SPSync adds a second column for the new type and flags the old one, keeping the history readable. Key columns are written without foreign key constraints, so load order never fails a run.

Running the first sync

The first run reads everything and later runs read deltas. The run’s events screen splits the totals by list: 260 product range items, 420 store audits and 25 suppliers. Each table is committed in a single transaction when its read finishes, so Metabase never queries a half-written table.

Metabase, a stock-check app and an agent on the same tables

Metabase connects to the replica as an ordinary PostgreSQL database, and the store compliance league table, which used to be rebuilt from an export before each regional call, now updates every five minutes. An internal app reads v_sp_product_range to check stock and range status before a buyer places an order, and a certificate expiry alert reads the suppliers view for anything due in the next thirty days. An Azure AI Foundry agent with a read-only SQL tool can answer which stores fell below a compliance score this month. For a comparison of dashboard tools, see Tableau, Looker and Metabase on SharePoint data.

Five-minute deltas and a weekly check

Set a five-minute interval and each run reads only what changed, keeping PostgreSQL a few minutes behind SharePoint at most. A full reconcile runs every seven days by default and re-checks every item, and you can also start one by hand as a QA check or after an outage such as your own server going down or a Microsoft 365 incident. The desktop app and SPSync in your Azure have a schedule picker. On the command line, spsync run performs one run, and cron, a scheduled task or a CI pipeline sets the interval.

When a store audit is edited or deleted

The next delta run picks up an edited item. A deleted item stays in its table with a deleted flag, and the reporting view leaves it out, so the league table matches SharePoint while an auditor can still find the audit in the raw table.

The same lists can go to more than one destination, one replication each, and the destinations page lists every database, warehouse, lake and search index SPSync supports. If the one you need is missing, tell us. Book a call to walk through your own lists; trials are arranged on that call, in your tenant.

Connect to SharePoint and sign in

What you see: The connect wizard asking for the retail site URL and a delegated sign-in, then the permissions wizard's one-page pack.

Why it matters: Store audit data stays inside the access the store ops account already has, and SPSync never writes to the retail site.

What it replaces: An app registration with tenant-wide Sites.Read.All that an admin has to justify for one retail site.

  1. Open the desktop app, choose Connect a site and sign in with the account that already has access to Product Range, Store Audits and Suppliers.

  2. Review the permissions wizard's one-page pack before approving Sites.Selected access for the Northfield Retail Group site.

Pick the lists and the PostgreSQL destination

What you see: Product Range, Store Audits and Suppliers ticked together, mapped onto a new PostgreSQL schema with a preview of every column and type.

Why it matters: The supplier lookup only resolves if Suppliers is replicated alongside Product Range, so the three lists belong in one replication.

What it replaces: A loader script per list, each needing its own fix whenever the range team adds a column.

  1. Tick Product Range, Store Audits and Suppliers, name the replication and choose the PostgreSQL destination.

  2. Turn on reporting views, then use Preview schema to check every column, its type and which columns are lookups before anything runs.

Run the first sync

What you see: 260 product range items, 420 store audits and 25 suppliers written, broken out by list on the run's own events screen.

Why it matters: After the first full read, each run touches only the audits and range items that changed, which keeps a five-minute schedule cheap.

What it replaces: A Metabase refresh that times out against the list.

  1. Click Save and run, open the run's own events and watch the per-list row counts arrive: 260 product range items, 420 store audits, 25 suppliers.

  2. Set the schedule to five minutes so the three-list replication checks for changes twelve times an hour.

See the result: lookups, child tables and joins

What you see: sp_product_range joined to sp_suppliers in a SQL query, and a Discontinued Date column added to sp_product_range automatically after a change in SharePoint.

Why it matters: Range reviews need product, supplier and audit data side by side, and PostgreSQL can join them where the list view cannot.

What it replaces: An export to a spreadsheet every time a range decision needs a join SharePoint cannot answer on its own.

  1. Open the destination database and browse sp_product_range, sp_store_audits and sp_suppliers; each keeps the SharePoint item id as its key.

  2. Run a join between sp_product_range and sp_suppliers on supplier_id, something no SharePoint list view answers directly.

Use the replica: Metabase, an internal app and an AI agent

What you see: A Metabase dashboard on the replica, an internal app reading a reporting view and an Azure AI Foundry agent answering questions with a read-only SQL tool.

Why it matters: Three different consumers read one copy of the data, and none of them sends a request to SharePoint.

What it replaces: A compliance league table rebuilt from an export before each regional call.

  1. Connect Metabase to the PostgreSQL database like any other and build the store compliance league table on v_sp_store_audits.

  2. Point an internal app at v_sp_product_range to check stock and range status, and give an AI agent a read-only SQL tool over the same replica.

Set it up

Signing in and connecting are covered in the SharePoint connection guide. For the database side, follow the PostgreSQL setup guide. If a connection test fails, see troubleshooting.

What you get

We'll set it up with you on the call: your use case, further walkthroughs or demos, and a trial in your own environment.

Frequently asked questions

How do you replicate a SharePoint list to PostgreSQL?

Sign in to SharePoint with delegated access or an admin-granted Sites.Selected app permission, pick the site and tick the lists, then give SPSync a PostgreSQL connection string. SPSync builds the tables itself and refreshes them on the schedule you set.

How does a SharePoint lookup column look in PostgreSQL?

A single lookup becomes a key column such as supplier_id that joins to the table built from the list it points at. Multi-value lookups and choices become child tables keyed back to the parent row, and people resolve against one sp_users table shared by every list in the database.

Can Metabase read the replicated tables directly?

Yes. Add the database in Metabase as an ordinary PostgreSQL connection. Lookups are columns you can join on, and the reporting views already drop soft-deleted rows, so a question in Metabase reads a plain table.

Will PostgreSQL enforce the relationship between Product Range and Suppliers?

Not by default. SPSync writes the supplier_id key column but leaves out the foreign key constraint, which means load order can never fail a sync. If you want PostgreSQL to enforce the relationship, add the constraint yourself.

Do deleted store audits disappear from PostgreSQL?

No. An audit deleted in SharePoint stays in sp_store_audits with a deleted flag, and v_sp_store_audits leaves it out, so dashboards match SharePoint while an auditor can still find the row. Edits arrive with the next delta run.

Is it safe to sync SharePoint data to a database?

Sign-in is delegated, so SPSync can read only what the signed-in account can read, and Sites.Selected narrows that to the sites you grant. SPSync never writes to SharePoint, and an admin can review the permissions wizard's one-page pack before approving anything.

What we covered

Compared with

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment. No obligation.