Repository navigation
Vector Search
Apps never connect to Postgres. Similarity search follows the same rule: your app sends a GraphQL query to Hasura, and Hasura runs a small SQL function that nself generates for you. The function returns rows of your own table, so the table's select permission filters every row before the app sees it. No new role, grant or connection string is created.
You need a table with a vector(n) column, tracked in Hasura, in a project that runs the pgvector image (the default for new projects; see pgvector).
nself db generate vector-search public.docs.embeddingIn v1.4 mode the same command is nself generate vector-search public.docs.embedding.
The argument is <schema>.<table>.<column>. nself reads the column type from the running Postgres container, so the stack must be up. It writes one migration:
migrations/<timestamp>_vector_search_docs_embedding.sql
The migration creates public.docs_nearest_embedding(query vector(n)). It is a STABLE SQL function that returns SETOF public.docs ordered by distance. Options:
| Flag | Meaning |
|---|---|
--distance cosine|l2|ip |
cosine (default) orders by <=>, l2 by <->, ip (inner product) by <#>
|
--roles user,staff |
Hasura roles that may call the function. Default: user
|
--track |
Track the function in Hasura (see below) |
--migration-dir DIR |
Where to write the migration. Default: ./migrations
|
--json |
Print one v1 JSON envelope |
Running the command again with the same options reuses the existing migration file. Changing --distance writes a new file, because the SQL changes.
The function has no row cap on purpose. A cap inside it would apply before the select permission filter and could return fewer rows than the caller is allowed to see. The caller passes limit in GraphQL instead.
nself db migrate up --migration-dir migrations
nself db generate vector-search public.docs.embedding --track--track sends one targeted Hasura call: track the function, then add a function permission for each role. It never replaces metadata and never runs nself db hasura metadata apply. A second run changes nothing.
Before it sends anything, nself checks the live metadata:
- The table must be tracked, and every role in
--rolesmust have a select permission on it. Otherwise E514, because a function granted to a role without select would show no rows. - Every role that has a select permission on the table must expose
selectat the root. A permission withquery_root_fieldsorsubscription_root_fieldsset to a list withoutselect(the relationship-only pattern) is refused with E514 ("table select is not exposed at the root"): Hasura still infers the function for it, and the function would return every row of the table. - Every role that has a select permission on the table must see the vector column (
columns: "*"or a list that includes it). Otherwise E514 naming each role and the reason. This applies to roles outside--rolestoo.
The second rule exists because Hasura, by default, infers function permissions from the table's select permissions: the function is visible to every role with select on the table. The function orders by the vector column inside Postgres, where column permissions do not apply, so a role that cannot read embedding could still learn it from the result order. nself refuses to track the function until every select role can see the column, and nothing is sent when the check fails. The generator (without --track) runs the same check against the on-disk metadata.
--roles adds explicit function permissions. They only gate access when Hasura runs with HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS=false; with the default inference, every select role can call the function.
The column check reads each role's own select permission. Inherited roles are not evaluated (their parent roles are), which is a known limitation: check the parents' columns.
The generator reads your project's on-disk Hasura metadata to confirm the table is tracked. Without it the command stops with E513. Create it with nself db hasura metadata export.
After --track, nself adds the function to the on-disk metadata only when that metadata matches live Hasura. If it differs (someone changed permissions in the console, for example), nself tracks the function live, leaves the files alone and prints a note. Run nself db hasura metadata export to refresh the files, then commit them.
Two layouts are updated: the hasura/metadata/sources.yaml that nself db hasura metadata export writes, and the older hasura/metadata.json (rewritten as sorted-key JSON with two-space indent). Any other layout is left unchanged.
Permissions changed after --track are not re-checked. After you change any select permission on the table, run nself db generate vector-search ... --track again: it re-validates every role and is a no-op when nothing else changed.
Before tracking, nself also checks the function in Postgres: it must be LANGUAGE sql, STABLE, not SECURITY DEFINER, without a SET clause, and return SETOF the table. A function that exists in another shape is refused with E513; regenerate it.
The migration is created with exclusive create. If a file with the same name exists, the command stops and leaves it untouched.
query Nearest {
docs_nearest_embedding(args: { query: "[0.1, 0.2, 0.3]" }, limit: 5) {
id
owner
}
}Send it with a normal user JWT. Hasura applies the docs select filter (for example owner = X-Hasura-User-Id) to the ordered rows, then applies limit. The caller gets only rows they may read, nearest first. A role that has no select permission on the table, including anonymous, gets a "field not found" error.
To set a server-side ceiling, put a limit on the role's select permission for the table. It also caps this function.
- One function per column. Running the command with another
--distancereplaces the function body (CREATE OR REPLACE), because the name<table>_nearest_<column>and the argument type stay the same. - The column must be
vector(n)with a fixed dimension.halfvec,sparsevecandtextcolumns are refused with E513. - The function name must fit in 63 bytes.
- With an
hnsworivfflatindex the approximate scan collects its candidates before the row filter runs, so a filtered role can get fewer rows thanlimit. Turn on pgvector's iterative scan (hnsw.iterative_scanorivfflat.iterative_scan, pgvector 0.8 or later), or use exact scans for small, heavily filtered sets. - Without an index, a search sorts the visible rows. For large tables create a pgvector index (
hnsworivfflat) on the column with the operator class that matches your distance. Because the function inlines into the caller's query, the planner can use it; check withEXPLAIN.
| Code | When |
|---|---|
| E513 | The column is not vector(n), the table is not in the on-disk metadata, there is no on-disk metadata, or the function is missing from Postgres when you run --track
|
| E514 | A role in --roles has no select permission on the table, or any role with a select permission hides the vector column or does not expose select at the root |
See error-codes.
Getting Started
Commands
- Commands, Overview
- Lifecycle: cmd-init · cmd-build · cmd-start · cmd-stop · cmd-restart · cmd-dev
- Monitoring: cmd-status · cmd-logs · cmd-health · cmd-urls · cmd-doctor · cmd-monitor · cmd-alerts · cmd-sentry · cmd-watchdog
- Data: cmd-db · cmd-backup · cmd-dr · cmd-queue · cmd-webhooks
- Config: cmd-config · cmd-service · cmd-env · cmd-promote
- Networking: cmd-ssl · cmd-trust · cmd-dns-setup
- Security: cmd-access · cmd-security · cmd-secrets
- Tenancy: cmd-tenant · cmd-billing
- Plugins: cmd-plugin · cmd-license · cmd-dogfood (extracted, CLI-R11) · cmd-k8s (extracted, CLI-R11) · cmd-encryption (extracted, CLI-R11) · cmd-waf (extracted, CLI-R11) · cmd-federation (extracted, CLI-R11) · cmd-mail (extracted, CLI-R11) · cmd-dlq (extracted, CLI-R11)
- AI: cmd-ai · cmd-claw · cmd-model
- Templates: cmd-template
- Utilities: cmd-exec · cmd-clean · cmd-reset · cmd-update · cmd-upgrade · cmd-version · cmd-admin · cmd-migrate · cmd-migrate-firebase · cmd-migrate-supabase · cmd-completion
Features
- Features, Overview
- Feature-Auth
- Feature-Storage
- Feature-Search
- Feature-Functions
- Feature-Email
- Feature-Monitoring
- Feature-Plugins
- Feature-nClaw, AI Assistant
- Feature-nChat, Messaging
- Feature-nFamily, Family Social
- Feature-nCloud, Managed Hosting
- Feature-Memory-Rooms, Knowledge Organization
- Feature-Agent-Dashboard, Agent Metrics
- Feature-Image-Generation, AI Image Generation
Configuration
- Configuration, Overview
- Config-Env-Vars
- Config-Postgres
- Config-Hasura
- Config-Auth
- Config-Nginx
- Shared-Proxy
- Config-Optional-Services
- Config-Custom-Services
- Config-System
Plugins (87 + 10 monitoring)
- Plugin-Overview
- Plugin-Catalog
- Plugin-Install
- Plugin-Licensing
- Plugin-Architecture
- Plugin-Dev-Guide
- Plugin-Migrations-Contract
Free (25)
- plugin-backup
- plugin-content-acquisition
- plugin-content-progress
- plugin-cron
- plugin-donorbox
- plugin-feature-flags
- plugin-github
- plugin-github-runner
- plugin-invitations
- plugin-jobs
- plugin-link-preview
- plugin-mdns
- plugin-mlflow
- plugin-monitoring
- plugin-notifications
- plugin-notify
- plugin-paypal
- plugin-search
- plugin-shopify
- plugin-stripe
- plugin-subtitle-manager
- plugin-tokens
- plugin-torrent-manager
- plugin-vpn
- plugin-webhooks
Pro (62)
- plugin-access-controls
- plugin-activity-feed
- plugin-admin-api
- plugin-analytics
- plugin-auth
- plugin-bots
- plugin-browser
- plugin-calendar
- plugin-cdn
- plugin-chat
- plugin-claw
- plugin-claw-budget
- plugin-claw-news
- plugin-claw-web
- plugin-cloudflare
- plugin-cms
- plugin-compliance
- plugin-ddns
- plugin-devices
- plugin-documents
- plugin-entitlements
- plugin-file-processing
- plugin-game-metadata
- plugin-geocoding
- plugin-geolocation
- plugin-google
- plugin-home
- plugin-idme
- plugin-knowledge-base
- plugin-linkedin
- plugin-livekit
- plugin-media-processing
- plugin-meetings
- plugin-moderation
- plugin-mux
- plugin-object-storage
- plugin-observability
- plugin-photos
- plugin-podcast
- plugin-post
- plugin-realtime
- plugin-retro-gaming
- plugin-rom-discovery
- plugin-social
- plugin-sports
- plugin-support
- plugin-tmdb
- plugin-voice
- plugin-web3
- plugin-workflows
Planned (26)
plugin-auditplugin-blogplugin-checkoutplugin-commerceplugin-drmplugin-exportplugin-flowplugin-importplugin-ldapplugin-mailgunplugin-mediaplugin-oauth-providersplugin-pagesplugin-postmarkplugin-rate-limitplugin-reportsplugin-samlplugin-schedulerplugin-sendgridplugin-ssoplugin-subscriptionplugin-thumbplugin-transcoderplugin-twilioplugin-wafplugin-watermark
Guides
- Guide-Production-Deployment
- Guide-SSL-Setup
- Guide-Multi-Tenancy
- Guide-Security-Hardening
- Guide-Monitoring-Setup
- Guide-Backup-Restore
- Guide-Custom-Services
- Guide-Migration-from-v1
Architecture
- Architecture
- Architecture-Guardrails
- Service-Graph
- Compose-Generation
- Nginx-Generation
- Plugin-Architecture
Reference
Machine interface
-
JSON-Output,
--jsonenvelope -
Command-Registry,
nself help --json - Exit-Codes
- error-codes, Error Codes
-
Compat-V15,
NSELF_V15gate
Licensing
Security
Brand
Operations
- operations/release-cascade, Release Cascade
- operations/self-healing, Self-Healing Schema
- operations/redis-tuning, Redis Pool Tuning
- operations/meilisearch-warmup, MeiliSearch Warm-Up
- operations/jwt-rotation, JWT Key Rotation
- operations/windows-wsl2-setup, Windows / WSL2 Setup
- operations/gemini-oauth-reauth, Gemini OAuth Reauth
Contributing
Admin
- USER-ACTION-QUEUE, Pending Admin Actions
All commands (21)
- A: cmd-add · cmd-admin
- B: cmd-backup · cmd-build
- C: cmd-clean · cmd-config
- D: cmd-db · cmd-deploy · cmd-doctor
- E: cmd-exec
- I: cmd-init
- L: cmd-license · cmd-logs
- M: cmd-mcp
- R: cmd-remove · cmd-reset · cmd-restart
- S: cmd-start · cmd-status · cmd-stop
- U: cmd-update