CoVectors
CoVectors let you store and query high‑dimensional vectors directly in Jazz apps. They are ideal for semantic search, or personalization features that work offline, sync across devices, and remain end‑to‑end encrypted.
The Journal example demonstrates semantic search using of CoVector.
CoVectors are defined using co.vector(), and are often used as fields in a CoMap within a CoList (making it easy to perform vector search across list items).
import { co, z } from "jazz-tools"; const Embedding = co.vector(384); // Define 384-dimensional embedding const Document = co.map({ content: z.string(), embedding: Embedding, }); export const DocumentsList = co.list(Document);
The number of dimensions matches the embedding model used in your app. Many small sentence transformers produce 384‑dim vectors; others use 512, 768, 1024 or more.
Creating CoVectors
You can create vectors in your Jazz application from an array of numbers, or Float32Array instance.
// Generate embeddings (bring your own embeddings model) const vectorData = await createEmbedding("Text"); const newDocument = Document.create({ content: "Text", embedding: Embedding.create(vectorData), }); documents.$jazz.push(newDocument);
Ownership
Like other CoValues, you can specify ownership when creating CoVectors.
// Create with shared ownership const teamGroup = Group.create(); teamGroup.addMember(colleagueAccount, "writer"); const teamList = co.vector(384).create(vector, { owner: teamGroup });
See Groups as permission scopes for more information on how to use groups to control access to CoVectors.
Immutability
CoVectors cannot be changed after creation. Instead, create a new CoVector with the updated values and replace the previous one.
Semantic Search
Semantic search lets you find data based on meaning, not just keywords. In Jazz, you can easily sort results by how similar they are to your search query.
You can load your data using the .load method, then compute and sort the results by similarity to your query embedding:
Wrapping each item with its similarity score makes it easy to sort, filter, and display the most relevant results. This approach is widely used in vector search and recommendation systems, since it keeps both the data and its relevance together for further processing or display.
Cosine Similarity
To compare how similar two vectors are, we use their cosine similarity. This returns a value between -1 and 1, describing how similar the vectors are:
1means the vectors are identical0means the vectors are orthogonal (i.e. no similarity)-1means the vectors are opposite direction (perfectly dissimilar).
If you sort items by their cosine similarity, the ones which are most similar will appear at the top of the list.
Jazz provides a built-in $jazz.cosineSimilarity method to calculate this for you.
Embedding Models
CoVectors handles storage and search, you provide the vectors. Generate embeddings with any model you prefer (Hugging Face, OpenAI, custom, etc).
Recommended: Run models locally for privacy and offline support using Transformers.js. Check our Journal app example to see how to do this.
The following models offer a good balance between accuracy and performance:
- Xenova/all-MiniLM-L6-v2 — 384 dimensions, ~23 MB
- Xenova/paraphrase-multilingual-mpnet-base-v2 — 768 dimensions, ~279 MB
- mixedbread-ai/mxbai-embed-large-v1 — 1024 dimensions, ~337 MB
- Browse more models →
Alternatively, you can generate embeddings using server-side or commercial APIs (such as OpenAI or Anthropic).
Best Practices
Changing embedding models
Always use the same embedding model for all vectors you intend to compare. Mixing vectors from different models (or even different versions of the same model) will result in meaningless similarity scores, as the vector spaces are not compatible.
If you need to switch models, consider storing the model identifier alongside each vector, and re-embedding your data as needed.