Last updated
Free-text columns in our lists hold useful information that keyword search over SharePoint never surfaces well.
Keyword search matches words, and incident notes rarely use the same words twice. “No hot water on floor three” and “boiler lockout, east wing” describe the same fault, and SharePoint’s search will never connect them. Free-text columns in a facilities incident list, what the engineer saw, what they tried, what fixed it, are the most useful part of the list and the hardest to search.
Embeddings written by the sync
SPSync replicates the lists, free-text columns included, into Postgres with pgvector and refreshes them on a schedule. On every sync it writes one embedding per row from the text columns you choose, so there is no separate embedding job to build or schedule. The vectors sit in the same table as the rest of the replicated data, with an HNSW index for similarity search, so one statement can filter on site or status and order by vector distance.
| Title | Client lookup | Tags multi-choice | Owner person | Due |
|---|---|---|---|---|
| Riverside | Acme Ltd | Design; Build | Ada Lovelace | 2026-11-30 |
| Harbour | Borealis plc | Build | Alan Turing | 2027-02-14 |
then sync
- id
- title
- client_id → clients.id
- owner_id → sp_users.id
- due
- is_deleted soft delete
- sp_modified
- id
- title
- …
- project_id
- tag
One row per value: Riverside has two.
- id
- display_name
One users table shared by every list.
Inside the incidents table
The free-text columns replicate as text columns next to everything else in the list:
lookups as key columns, multi-value columns as child tables, and a stable primary key per
item. The text columns you configure are joined, in order, into one piece of text per row,
and its embedding goes into a single vector column, sp_embedding by default. A removed
incident keeps its row and vector with sp_deleted_at set.
What re-embedding costs
Delta sync identifies which rows changed since the last run, and only those are re-embedded. Embedding cost and latency therefore follow how much text changes between syncs: twenty new or edited incidents in a day cost twenty embeddings that day. Set the schedule by how often the free text changes.
To set it up, point SPSync at a PostgreSQL 15 or later database with the pgvector extension available, pick the lists, and choose which text columns to embed and which embedding model to use. Your application then queries the relational columns and the vectors together. SharePoint to pgvector covers the full setup, the model configuration and the schema. While you build and test, the desktop app is enough; a production search needs the sync running somewhere that stays on.
The article comparing a flow, a script and replication for embedding SharePoint lists sets a Power Automate flow, a LangChain or LlamaIndex script and the Azure AI Search indexer against embedding on sync, using a facilities Incidents list and a similarity query in Supabase.
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 semantic_search_embeddings --prefix semantic_search_embeddings --destination-kind postgres --connection-string-env SPSYNC_DB_CONNECTION --dataset-name semantic_search_embeddings --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: semantic_search_embeddings
prefix: semantic_search_embeddings
auth:
mode: delegated
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: common
destination:
kind: postgres
connection_string_env: SPSYNC_DB_CONNECTION
dataset_name: semantic_search_embeddings
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.