Search
Source:
src/Cloudflare/AI/Search.ts
A convenience construct over {@link SearchInstance} that auto-creates the
sub-resources an AI Search instance typically needs, so a single call wires
up a working pipeline. The data source is chosen by what you pass as
source — an {@link Bucket} for R2, or a URL for a web crawl:
- For an R2 source, it mints a least-privilege {@link AccountApiToken}
(
AI Search Index Engine, stable childApiToken) and an {@link SearchToken} wrapping it (stable childToken), then passes that token to the instance. Cloudflare requires a service token to read an R2 bucket and only provisions one through the dashboard / Wrangler — never on a programmatic API create — so the construct provisions it for you. Pass your owntokenIdto skip minting and reuse an existing token. - It creates the {@link SearchInstance} (child
SearchInstance) with the remaining props.
Drop down to the low-level resources directly when you need to share a token across instances, adopt an existing one, or bind a namespace.
The returned value is an {@link SearchInstance} (augmented with the
managed serviceToken, undefined for a web crawler), so a Search
is usable anywhere a SearchInstance is expected — pass it straight to
Cloudflare.AI.QuerySearch(search) or a Worker’s env.
Creating an AI Search pipeline
Section titled “Creating an AI Search pipeline”R2-backed instance (token provisioned for you)
Pass an {@link Bucket} as source — its presence selects R2.
const bucket = yield* Cloudflare.R2.Bucket("docs");const search = yield* Cloudflare.AI.Search("docs-search", { source: bucket,});Index only part of a bucket
const search = yield* Cloudflare.AI.Search("docs-search", { source: bucket, prefix: "docs/", include: ["/docs/**"], exclude: ["/docs/drafts/**"],});Reuse an existing service token
const search = yield* Cloudflare.AI.Search("docs-search", { source: bucket, tokenId: existingToken.id,});Web-crawler source
Pass a URL as source to crawl and index a website (no service token
needed). parse.type defaults to "sitemap"; use "crawl" to follow
links from the seed instead.
const search = yield* Cloudflare.AI.Search("site-search", { source: "https://example.com", parse: { type: "crawl", contentSelector: [{ path: "/docs", selector: "main" }] }, crawl: { depth: 3, includeSubdomains: true },});Store crawl output in your own bucket
const store = yield* Cloudflare.R2.Bucket("crawl-store");const search = yield* Cloudflare.AI.Search("site-search", { source: "https://example.com", parse: { type: "crawl" }, store: { bucket: store },});Binding to an Effect Worker
Section titled “Binding to an Effect Worker”The returned search is an {@link SearchInstance}. Bind it during the
Worker’s init phase with Cloudflare.AI.QuerySearch(search), which
attaches the single-instance ai_search binding and hands back an
Effect-native client whose search / chatCompletions methods return
Effects. Provide Cloudflare.AI.QuerySearchBinding in the Worker’s
runtime layer.
import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
export default class Api extends Cloudflare.Worker<Api>()( "api", { main: import.meta.url }, Effect.gen(function* () { const bucket = yield* Cloudflare.R2.Bucket("docs"); const aiSearch = yield* Cloudflare.AI.Search("docs-search", { source: bucket, }); const search = yield* Cloudflare.AI.QuerySearch(aiSearch);
return { fetch: Effect.gen(function* () { const request = yield* HttpServerRequest; const query = new URL(request.url).searchParams.get("q") ?? ""; const answer = yield* search.chatCompletions({ messages: [{ role: "user", content: query }], }); return yield* HttpServerResponse.json(answer); }), }; }).pipe(Effect.provide(Cloudflare.AI.QuerySearchBinding)),) {}Binding to an Async Worker
Section titled “Binding to an Async Worker”For a vanilla async fetch Worker, pass the search under Worker.env.
The engine attaches the same single-instance ai_search binding (see
toBinding in WorkerAsyncBindings.ts), orders the deploy
bucket → instance → worker, and InferEnv types env.SEARCH as the
runtime SearchInstance handle — no hand-written types.
const bucket = yield* Cloudflare.R2.Bucket("docs");const search = yield* Cloudflare.AI.Search("docs-search", { source: bucket,});
export const Api = Cloudflare.Worker("api", { main: "./worker.ts", env: { SEARCH: search },});export type ApiEnv = Cloudflare.InferEnv<typeof Api>;
// worker.tsimport type { ApiEnv } from "./stack.ts";export default { async fetch(request: Request, env: ApiEnv): Promise<Response> { const query = new URL(request.url).searchParams.get("q") ?? ""; const answer = await env.SEARCH.chatCompletions({ messages: [{ role: "user", content: query }], }); return Response.json(answer); },};