Skip to content

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 child ApiToken) and an {@link SearchToken} wrapping it (stable child Token), 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 own tokenId to 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.

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 },
});

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)),
) {}

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.

stack.ts
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.ts
import 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);
},
};