SharePoint to Postgres with pgvector, embeddings generated on the way

PostgreSQL 15 or later with pgvector, holding SharePoint list rows and the embeddings SPSync generates for the text columns you pick.

Book a call

Talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.

Last updated

One way: SharePoint stays the system of record, and SPSync keeps a read-only copy of your lists current in Postgres with pgvector.

Status: Shipped · product: SPSync for Search & Vector Stores

SPSync replicates SharePoint lists one way into Postgres and, on every sync, writes the vectors itself: an embedding per row from the text columns you pick, made with OpenAI, Azure OpenAI or a local model. Only rows whose text changed are embedded again. pgvector puts similarity search inside Postgres, next to the columns you filter on, so one query can filter on status, owner or date and rank by meaning in the same statement, which is what most people mean by semantic search over SharePoint.

Requirements

How are SharePoint embeddings built in Postgres?

You list the text columns to embed, for example title and summary. SPSync joins their values in that order into one piece of text per row and stores its embedding in a vector(n) column, sp_embedding by default, on the same table, with an HNSW index for fast similarity search. You can send vectors to a side table instead.

A companion sp_embedding_hash column records a hash of each row’s source text and the model name. Each run embeds only rows whose hash changed, so the embedding bill follows how much text changes, not the size of the list.

The relational side matches the Postgres destination: one table per list, {col}_id and {col}_value for lookups, {col}_user_id for people joined to sp_users, and a child table per multi-value column. One difference: a SharePoint column that changes type is not forked into a _v2 column on pgvector today, because embeddings are found by column name.

Uses

Teams building semantic search over free-text columns keep the source rows and the vectors in one database. A RAG pipeline uses pgvector as both the structured store and the vector store. Chat over SharePoint data and AI agents query it with a filter and a distance in one statement, with no separate vector service.

Steps

  1. Give SPSync SharePoint access: delegated sign-in, or Sites.Selected app permissions on the sites, approved by an admin, for runs with no one signed in.
  2. Store the Postgres connection string in an environment variable.
  3. Choose the provider, model, dimensions and text columns to embed.
  4. Run spsync doctor to check the server version and extension, then run the sync. The first run creates the tables, the vector column and the HNSW index.
  5. Point your retrieval code at the same database.

Row-level security applies to the replicated tables as to any Postgres table, which helps when different users should see different rows in a search result. Hard delete cannot be combined with the side-table option, because the side table is only ever added to.

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

Does SPSync generate the embeddings, or only replicate the text?

Both. SPSync replicates the list into Postgres tables, then embeds the text columns you choose for every row that is new or whose text changed since the last run.

Which embedding providers can I use?

OpenAI, Azure OpenAI, or a local model that runs on the sync host with no API key. A local model's weights download and are cached on first use.

Can I query the relational columns and the vectors together?

Yes, in one SQL statement. Filter on a column such as status or owner_user_id, exclude deleted rows, and order by distance to sp_embedding.

What happens if I change the embedding model?

SPSync records the model and dimensions it used for each table. A new model is detected on the next run and the whole table is re-embedded, so one column never mixes vectors from two models. A new dimension count goes into a fresh sp_embedding_v2 column.

What happens to embeddings when an item is deleted?

The row and its vector stay, with sp_deleted_at set, as on any Postgres destination. Add WHERE sp_deleted_at IS NULL to a similarity query to leave deleted items out.

Talk to us about Postgres with pgvector, embeddings generated on the way

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 pgvector setup guide to create the database user and open the connection for Postgres with pgvector, embeddings generated on the way. 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.

spsync auth login --client-id 00000000-0000-0000-0000-000000000000
spsync export-config --output replication.yaml --pipeline-name pgvector --prefix pgvector --destination-kind pgvector --connection-string-env SPSYNC_DB_CONNECTION --dataset-name pgvector --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: pgvector
prefix: pgvector
auth:
  mode: delegated
  client_id: 00000000-0000-0000-0000-000000000000
  tenant_id: common
destination:
  kind: pgvector
  connection_string_env: SPSYNC_DB_CONNECTION
  dataset_name: pgvector
scopes:
  - site_url: https://contoso.sharepoint.com/sites/example
    mode: entire_site
reconcile_interval_days: 7
create_reporting_views: false

Cron (pgvector 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

Tell us about your lists and where they need to go: we will talk through your use case, show further walkthroughs or demos, and set up a trial in your own environment.