1---
2title: Vector search with Vectorize
3description: Create a Vectorize index, bind it to a Worker with SearchIndex, and run filtered nearest-neighbor queries over vector embeddings.
4sidebar:
5 order: 21
6---
7
8Vectorize is Cloudflare's globally distributed vector database.
9In alchemy it's three pieces: a `Vectorize.Index` resource that
10holds the vectors, an optional `Vectorize.MetadataIndex` that makes
11a metadata property filterable, and a `Vectorize.SearchIndex`
12binding that gives a Worker a typed client to insert and query.
13
14## Create an index
15
16Declare the index once at module scope so both the stack and the
17Worker can reference it:
18
19```typescript
20// src/embeddings.ts
21import * as Cloudflare from "alchemy/Cloudflare";
22
23export const Embeddings = Cloudflare.Vectorize.Index("Embeddings", {
24 dimensions: 768,
25 metric: "cosine",
26});
27```
28
29`dimensions` must match the embedding model you'll use (768 for
30`bge-base`, 1536 for `text-embedding-ada-002`, …) and `metric` is
31one of `cosine`, `euclidean`, or `dot-product`. A Vectorize index is
32immutable — changing dimensions, metric, preset, or description
33triggers a replacement, and the replacement is a fresh, empty index.
34
35:::note
36Instead of `dimensions` + `metric`, you can pass a managed embedding
37model preset — `preset: "@cf/baai/bge-base-en-v1.5"` — which fixes
38both to match the named model.
39:::
40
41## Index a metadata property
42
43To filter query results by a metadata property, that property needs
44a metadata index. Create it after resolving the parent index:
45
46```typescript
47const index = yield* Embeddings;
48
49yield* Cloudflare.Vectorize.MetadataIndex("KindMeta", {
50 indexName: index.indexName,
51 propertyName: "kind",
52 indexType: "string",
53});
54```
55
56`indexType` is `string`, `number`, or `boolean`, matching the values
57stored under `propertyName`. Create metadata indexes **before**
58inserting vectors — only vectors inserted after the metadata index
59exists are filterable. Like the parent, a metadata index is
60immutable, and replacing the parent index replaces it too.
61
62## Bind the index to a Worker
63
64`Cloudflare.Vectorize.SearchIndex(index)` in the Worker's Construction phase
65attaches the native `vectorize` binding at deploy time and resolves
66to an Effect-native client at runtime:
67
68```typescript
69// src/worker.ts
70import * as Cloudflare from "alchemy/Cloudflare";
71import * as Effect from "effect/Effect";
72import { HttpServerRequest } from "effect/http/HttpServerRequest";
73import * as HttpServerResponse from "effect/http/HttpServerResponse";
74import { Embeddings } from "./embeddings.ts";
75
76export default Cloudflare.Worker(
77 "Search",
78 { main: import.meta.url },
79 Effect.gen(function* () {
80 const index = yield* Embeddings;
81 const vec = yield* Cloudflare.Vectorize.SearchIndex(index);
82
83 return {
84 fetch: Effect.gen(function* () {
85 const request = yield* HttpServerRequest;
86 // routes below use `vec`
87 return HttpServerResponse.text("ok");
88 }),
89 };
90 }).pipe(Effect.provide(Cloudflare.Vectorize.SearchIndexBinding)),
91);
92```
93
94The client exposes `upsert`, `insert`, `query`, `queryById`,
95`getByIds`, `deleteByIds`, and `describe`, plus `raw` for direct
96access to the underlying runtime binding.
97
98## Insert vectors
99
100`upsert` writes vectors, replacing any with matching ids
101(`insert` instead fails on duplicate ids):
102
103```typescript
104const mutation = yield* vec.upsert([
105 { id: "doc-1", values: embedding, metadata: { kind: "article" } },
106 { id: "doc-2", values: other, metadata: { kind: "note" } },
107]);
108// mutation.mutationId identifies the async write
109```
110
111Each `values` array must have exactly the index's `dimensions`
112entries. `metadata` is an arbitrary JSON object stored alongside the
113vector — properties covered by a metadata index become filterable.
114
115:::note
116Vectorize mutations are asynchronous — `upsert` returns a
117`mutationId` immediately, and a query right after may not see the
118new vectors yet. Poll with a bounded `Effect.repeat` when a test or
119follow-up read depends on the write being visible.
120:::
121
122## Query nearest neighbors
123
124`query` finds the vectors closest to the one you pass:
125
126```typescript
127const results = yield* vec.query(queryEmbedding, {
128 topK: 5,
129 returnMetadata: "all",
130});
131// results.matches: [{ id, score, metadata }, ...] best match first
132```
133
134`topK` caps the number of matches, and `returnMetadata: "all"`
135includes each match's stored metadata in the response.
136
137## Filter by metadata
138
139Pass a `filter` to restrict matches to vectors whose metadata
140satisfies it — this is what the metadata index enables:
141
142```typescript
143const articles = yield* vec.query(queryEmbedding, {
144 topK: 5,
145 returnMetadata: "all",
146 filter: { kind: { $eq: "article" } },
147});
148```
149
150Only vectors with `kind === "article"` are considered. A property
151without a metadata index cannot be used in a `filter`, so declare
152the `MetadataIndex` up front.
153
154## Deploy it
155
156The stack ties it together — index, metadata index, then the Worker
157that binds them:
158
159```typescript
160// alchemy.run.ts
161import * as Alchemy from "alchemy";
162import * as Cloudflare from "alchemy/Cloudflare";
163import * as Effect from "effect/Effect";
164import { Embeddings } from "./src/embeddings.ts";
165import Worker from "./src/worker.ts";
166
167export default Alchemy.Stack(
168 "VectorSearch",
169 {
170 providers: Cloudflare.providers(),
171 state: Cloudflare.state(),
172 },
173 Effect.gen(function* () {
174 const index = yield* Embeddings;
175 yield* Cloudflare.Vectorize.MetadataIndex("KindMeta", {
176 indexName: index.indexName,
177 propertyName: "kind",
178 indexType: "string",
179 });
180 const worker = yield* Worker;
181 return { url: worker.url.as<string>() };
182 }),
183);
184```
185
186```sh
187bun alchemy deploy
188```
189
190## Where next
191
192- [AI Search](/cloudflare/ai/ai-search) — managed RAG that
193 handles embedding and indexing for you; reach for Vectorize when
194 you want control over the vectors themselves.
195- [Workers](/cloudflare/compute/workers) — the two-phase Worker model the
196 `SearchIndex` binding lives inside.
197- Reference: [Index](/providers/cloudflare/vectorize/index),
198 [MetadataIndex](/providers/cloudflare/vectorize/metadataindex),
199 [SearchIndex](/providers/cloudflare/vectorize/searchindex)