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.
Foir automatically generates vector embeddings for your content and 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 |
+-----------------------+- Embedding generation — When you create or update a record, Foir extracts text from configured fields and converts it into a numerical vector.
- Storage — The vector is stored alongside the record, scoped to your project.
- Search — When a query comes in, it is converted to a vector and compared against stored vectors using cosine similarity.
- Results — Records are ranked by how closely their meaning matches the query.
Content Sources
Semantic search works with two types of content:
| Source | Description | Example |
|---|---|---|
| Entity | Versioned content with publishing workflows | Pages, blog posts, product descriptions |
| Data | Flat records with direct CRUD access | FAQ entries, support articles, knowledge base items |
You can search across both sources simultaneously or filter to one type.
In the Admin
Enabling Embeddings on a Model
- Navigate to Settings > Models
- Select the model you want to enable for semantic search
- Under Capabilities, toggle Embeddings on
- Choose which fields to include in the embedding (text fields, rich text, etc.)
- Save the model
Once enabled, Foir generates embeddings for existing records in the background and automatically embeds new or updated records going forward.
Searching in the Admin
The admin dashboard includes a global search bar that uses semantic search when enabled:
- Click the Search icon or press
/ - Type a natural language query
- Results are ranked by relevance across all embedding-enabled models
- Click a result to navigate directly to the record
Via the CLI
Searching
# Semantic search across all models
foir search "lightweight running shoes" --limit 10
# Search specific models
foir search "return policy for damaged items" --models faq,support-article --limit 5Managing 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 5See foir embeddings for the full subcommand and option reference.
Via the API
semanticSearch Query
Search your content by meaning using vector similarity. Returns records ranked by how closely they match your query.
Required scope: search:read
query SearchProducts {
semanticSearch(input: {
query: "lightweight shoes for trail running"
modelKeys: ["product"]
source: RECORD
limit: 10
threshold: 0.7
}) {
recordId
source
modelKey
naturalKey
similarity
highlights
}
}Input Fields
| Field | Type | Required | Description |
|---|---|---|---|
query | String | Yes | Natural language search query |
modelKeys | [String] | No | Filter by specific model keys |
source | EmbeddingSource | No | RECORD — currently the only source type |
limit | Int | No | Maximum results to return (default: 10) |
threshold | Float | No | Minimum similarity score 0-1 (default: 0.0) |
Response Fields
| Field | Type | Description |
|---|---|---|
recordId | ID | The matching record’s ID |
source | EmbeddingSource | Always RECORD |
modelKey | String | The model key of the matching record |
naturalKey | String | The record’s natural key (slug, handle, etc.) |
similarity | Float | Similarity score from 0 to 1 (higher = more similar) |
highlights | [String] | Relevant text excerpts from the matching content |
Search Across All Content
query GlobalSearch {
semanticSearch(input: {
query: "how to set up two-factor authentication"
limit: 5
threshold: 0.6
}) {
recordId
modelKey
naturalKey
similarity
}
}embeddingStatus Query
Check whether specific records have embeddings generated.
Required scope: search:read
query CheckEmbeddings {
embeddingStatus(
recordIds: ["rec_abc123", "rec_def456"]
source: RECORD
) {
recordId
hasEmbedding
model
dimensions
lastUpdated
}
}| Field | Type | Description |
|---|---|---|
recordId | ID | The record ID |
hasEmbedding | Boolean | Whether an embedding exists |
model | String | Embedding model used (e.g., text-embedding-3-small) |
dimensions | Int | Vector dimensions |
lastUpdated | DateTime | When the embedding was last generated |
generateEmbedding Mutation
Manually generate an embedding for a specific record. Useful when you need a record to be immediately searchable after creation.
Required scope: records:write
mutation EmbedRecord {
generateEmbedding(
recordId: "rec_abc123"
modelKey: "product"
source: RECORD
) {
success
embeddingId
recordId
tokenCount
dimensions
skipReason
}
}| Field | Type | Description |
|---|---|---|
success | Boolean | Whether embedding was generated |
embeddingId | ID | The generated embedding ID |
recordId | ID | The record ID |
tokenCount | Int | Tokens used for embedding |
dimensions | Int | Vector dimensions |
skipReason | String | Why embedding was skipped (e.g., content unchanged) |
If the content has not changed since the last embedding, the operation is skipped automatically and skipReason indicates why.
Error Handling
| Code | Description |
|---|---|
FORBIDDEN | API key lacks required scope |
AI_DISABLED | Search features not enabled for project |
NOT_FOUND | Record or model not found |
VALIDATION_ERROR | Invalid 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
modelKeysfiltering when you know which content types are relevant to narrow results and improve relevance. - Re-generate embeddings after significant content updates using
foir embeddings writeor thegenerateEmbeddingmutation.
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.