Skip to Content
FeaturesAI & Search

AI & Search

Foir includes built-in semantic search that lets you find content by meaning rather than exact keywords. No external machine learning infrastructure required.

Overview

Traditional search matches keywords. Semantic search understands meaning. When a user searches for “affordable running shoes,” semantic search also finds content about “budget sneakers for jogging” — even if none of those exact words appear.

You generate vector embeddings for your content, and Foir stores them alongside your records. You query with natural language, and the platform returns the most relevant results ranked by similarity.

How Embeddings Work

Embeddings are the foundation of semantic search. Here is the high-level flow:

Your Content Vector Space +-----------------------+ +-----------------------+ | "Lightweight | | | | running shoes | --> | [0.12, -0.34, | | for beginners" | Embed | 0.87, ...] | +-----------------------+ +-----------------------+ User Query Similarity Match +-----------------------+ +-----------------------+ | "beginner | | Score: 0.91 | | jogging gear" | --> | -> running shoes | | |Search | Score: 0.85 | +-----------------------+ | -> starter kit | +-----------------------+
  1. Embedding generation — You trigger embedding for a record (via the generateEmbedding mutation or foir embeddings), and Foir extracts its text and converts it into a numerical vector.
  2. Storage — The vector is stored alongside the record, scoped to your project.
  3. Search — When a query comes in, it is converted to a vector and compared against stored vectors using cosine similarity.
  4. Results — Records are ranked by how closely their meaning matches the query.

Content Sources

Semantic search works with two types of content:

SourceDescriptionExample
EntityVersioned content with publishing workflowsPages, blog posts, product descriptions
DataFlat records with direct CRUD accessFAQ entries, support articles, knowledge base items

You can search across both sources simultaneously or filter to one type.

In the Admin

Embeddings in the Admin

There is no toggle to “enable” embeddings on a model and no field-selection step. A model becomes searchable as soon as its records have stored embeddings, which you produce explicitly with the generateEmbedding mutation or the foir embeddings CLI.

To review coverage for a model:

  1. Navigate to Settings > Models
  2. Select the model you care about
  3. Open the Embeddings tab

The Embeddings tab is read-only. It reports how many records are embedded, the total record count, coverage percentage, and how many records are still pending. It appears only once the model has at least one stored embedding.

Searching in the Admin

The admin dashboard includes a global search bar backed by semantic search:

  1. Click the Search icon or press /
  2. Type a natural language query
  3. Results are ranked by relevance across all models that have embeddings
  4. Click a result to navigate directly to the record

Via the CLI

Searching

# Semantic search across all models foir search "lightweight running shoes" --first 10 # Search specific models foir search "return policy for damaged items" --models faq,support-article --first 5

Managing Embeddings

# Write an embedding for a record (input payload describes the record and vector) foir embeddings write --file ./embedding.json # Delete the embedding for a specific record foir embeddings delete rec_abc123 --confirm # Search by vector similarity foir embeddings search --data '{"query": "beginner jogging gear", "modelKeys": ["product"], "limit": 5}' # List embeddings stored for a record foir embeddings list rec_abc123 # Stats for a specific model foir embeddings stats product --json # Project-wide embedding stats (no model key) foir embeddings stats --json # Find records similar to a given one foir embeddings similar rec_abc123 --model-key product --limit 5

See foir embeddings for the full subcommand and option reference.

Via the API

Search is per-model typed. Every model that has embeddings gets its own search<Model>s query generated from its key — for a model keyed product that is searchProducts. A generic searchRecords escape hatch is always available for cross-model search.

Returns a typed list of { score, record } hits, where record is the model’s normal GraphQL type — so you can select any of its fields.

Required scope: search:read:<model> (e.g. search:read:product)

query SearchProducts { searchProducts( query: "lightweight shoes for trail running" first: 10 locale: "en" ) { score record { _id title price } } }
ArgumentTypeRequiredDescription
queryStringYesNatural language search query
firstIntNoMaximum number of hits to return (default: 10)
localeStringNoLocale to search within
Result fieldTypeDescription
scoreFloatSimilarity score (higher = more similar)
record<Model>The matching record, with all its typed fields

Generic Search Across Models

When you want to search across model types, use searchRecords. It returns generic SearchResult rows rather than a typed record.

Required scope: search:read

query GlobalSearch { searchRecords( query: "how to set up two-factor authentication" modelKey: "support_article" # optional; omit to search all models that have embeddings first: 5 ) { recordId modelKey naturalKey score content } }
ArgumentTypeRequiredDescription
queryStringYesNatural language search query
modelKeyStringNoLimit to a single model key
firstIntNoMaximum number of hits to return
Result fieldTypeDescription
recordIdStringThe matching record’s ID
modelKeyStringThe model key of the matching record
naturalKeyStringThe record’s natural key (slug, handle, etc.)
scoreFloatSimilarity score (higher = more similar)
contentJSONThe matched record content

Embedding Coverage and Maintenance

A small set of operator queries and mutations let you inspect and manage stored embeddings directly. The coverage/inspection queries require search:semantic:read; the write mutations require search:semantic:write.

# Which records are missing embeddings for a model (Relay connection) query Missing { recordsMissingEmbedding(modelKey: "product", first: 50) { edges { node { recordId naturalKey } } } } # Per-model coverage (omit modelKey for every model) query Coverage { embeddingCoverage(modelKey: "product") { modelKey totalRecords embeddedRecords pendingRecords lastEmbeddedAt } }

generateEmbedding Mutation

Manually enqueue an embedding for a specific record. Useful when you need a record to be searchable immediately after creation.

Required scope: search:semantic:write

mutation EmbedRecord { generateEmbedding( recordId: "rec_abc123" modelKey: "product" ) }

The mutation returns a Boolean indicating whether the embedding job was enqueued. If the content has not changed since the last embedding, the job is skipped on the worker side. writeEmbeddings (also search:semantic:write) lets you push precomputed vectors directly.

Error Handling

Errors surface in the GraphQL response’s extensions.code:

CodeDescription
PERMISSION_DENIEDAPI key lacks the required scope
FAILED_PRECONDITIONEmbeddings are not configured for this project
NOT_FOUNDRecord or model not found
INVALID_ARGUMENTInvalid input parameters

Best Practices

  • Set a similarity threshold to filter out low-quality matches. A threshold of 0.6-0.7 works well for most use cases.
  • Choose embedding fields carefully — include the fields that best describe your content (titles, descriptions, body text) and exclude metadata fields.
  • Use modelKeys filtering when you know which content types are relevant to narrow results and improve relevance.
  • Re-generate embeddings after significant content updates using foir embeddings write or the generateEmbedding mutation.

Use Cases

  • E-commerce product discovery — Let customers describe what they are looking for in natural language instead of navigating category trees.
  • Help center / FAQ search — Feed support articles into the search index and let users find answers by asking questions in plain English.
  • Content recommendations — Find similar articles, products, or pages based on meaning rather than tags using foir embeddings similar.
  • Internal search — Build a search experience across all your project content that understands intent, not just keywords.
Last updated on