Release Notes

Version 1.3.0

📅 Release Date

September 11, 2026

📖 Overview

Eight accessors, closing gaps in the package's Cloudflare binding coverage. Every previously supported Cloudflare binding had had a Binding subclass since 1.0.0 — AI, Analytics Engine, D1, Durable Objects, Hyperdrive, KV, Queues, R2, Rate Limiting, Vectorize — except Browser Rendering, Workflows, Flagship, Pipelines, and the two halves each of AI Search and Workers VPC.

Browser Rendering was a gap found during real-world use. A consuming Worker reached env.BROWSER directly, so a deployment that had not declared the binding passed undefined into a browser automation library and failed somewhere inside it, in that library's terms rather than in terms of the wrangler.json that was missing a line. Every other resource in this package answers that case with a MissingBindingError naming the binding.

Workflows and Flagship close the same gap before consumers need to reach for env directly. Adding the accessors before adoption means there is no direct binding access to migrate later.

AI Search rounds out the package's binding coverage. It is two bindings rather than one — ai_search names a single instance at deploy time, ai_search_namespaces opens a namespace so a request can name the instance — and the package needed both, since covering half of a product is the gap it just spent four accessors closing.

It also arrives with a correction attached. The older path to this product hung off the Workers AI binding, as env.AI.autorag(id), and that is deprecated in the @cloudflare/workers-types version this package pins. AIBinding has existed since 1.0.0, so the obvious move would have been to expose AI Search through it — and would have adopted a deprecated surface on the day it was written.

Pipelines supports archiving raw records — including records that validation rejects — into R2 without changing a downstream storage contract. It is the first binding here whose type is not global: Pipeline lives inside declare module "cloudflare:pipelines", so lib/index.d.ts now carries a module import where every other binding type resolved from the ambient scope. rollup.config.ts already listed /^cloudflare:/ as external, so the packaging held — the emitted declaration keeps the import and the runtime bundle carries no trace of it, since the import is type-only.

Workers VPC reaches services on a private network — an internal API, a database, something in an AWS VPC or on a rack — through a Cloudflare Tunnel, with nothing exposed to the internet. It is two bindings: vpc_services pins one host and port at deploy time, vpc_networks opens a whole tunnel or mesh and lets each call name its destination.

It is also the first addition here that required this package to describe a binding shape itself. Workers VPC is in beta, and @cloudflare/workers-types declares nothing for it at the version pinned — so while a VPC Service is Fetcher structurally and needed nothing invented, a VPC Network's connect had no type to borrow. VpcNetwork is therefore written here, kept to the two documented methods, and meant to be deleted when the real type ships.

Nothing else changes. No existing export is touched.

⚠️ Breaking Changes

  • No export is removed, renamed, or narrowed. Seventeen are added — eight accessors, eight settings shapes, and the one binding shape this package had to declare itself.

  • But CloudflareEnv gaining members is not purely additive, and this release breaks a real consumer. This was found while adopting the release, after publication, and is corrected here rather than left to be discovered.

    A Worker whose Env extends CloudflareEnv and which already declared one of the new binding names itself now fails to compile, because its own declaration must be assignable to the one it inherits. One consumer had:

    export interface Env extends CloudflareEnv {
      BROWSER?: BrowserWorker // from @cloudflare/puppeteer
    }
    

    BrowserWorker is { fetch: typeof fetch } — narrower than the Fetcher that CloudflareEnv now declares — so upgrading produced TS2430: Interface Env incorrectly extends interface CloudflareEnv, plus five cascading TS2345/TS2344 errors everywhere that Env was passed where a CloudflareEnv was expected.

    The fix is to delete the local declaration, which the inherited one now supersedes. See ⬆️ Upgrading.

    The names to check are BROWSER, WORKFLOW, FLAGS, PIPELINE, AI_SEARCH, AI_SEARCH_NAMESPACE, VPC_SERVICE and VPC_NETWORK. A Worker that declared none of them is unaffected.

    CloudflareConfiguration gains eight required members: aiSearch, aiSearchNamespace, browser, flagship, pipeline, vpcNetwork, vpcService and workflow. That is source-compatible for every consumer building its configuration from cloudflareDefaults, which is the documented way and supplies all eight. A consumer that hand-writes the whole interface instead needs to add them.

🚀 Features

  • BrowserBinding — the accessor for Browser Rendering, over Binding like the other ten. Defaults to the BROWSER binding name, takes a BrowserSettings override, and inherits resolve, tryResolve, isBound, and name.

  • BrowserSettings, a BindingSettings carrying the binding name and nothing else, plus a browser module in cloudflareDefaults and CloudflareConfiguration. BROWSER?: Fetcher joins CloudflareEnv.

    Fetcher rather than a browser-library type, deliberately: it is what the runtime hands over, and naming it costs no dependency.

  • WorkflowBinding — the producer side of Cloudflare Workflows, over Binding like the rest. create, createBatch and get keep the platform's own names so Cloudflare's documentation still describes this class, and status(env, id) is the one addition: a status endpoint reads a run on every request, and would otherwise await twice to do it.

    Unlike Browser Rendering, the operations here cost nothing to offer — Workflow, WorkflowInstance and their options all come from @cloudflare/workers-types, already a peer.

  • WorkflowSettings, carrying the binding name plus successRetention and errorRetention, with a workflow module in cloudflareDefaults and CloudflareConfiguration. WORKFLOW?: Workflow joins CloudflareEnv.

    Retention is settings rather than a call argument for the same reason KV's expirationTtl is: it is a deployment policy, and a Worker that forgets it should get a bounded answer rather than the longest period the account allows. A call that names retention itself owns both halves of it — no per-field merge, so asking for a longer error retention on one run never silently inherits the configured success retention beside it.

  • FlagshipBinding — feature-flag evaluation at request time, so a value that is a vars entry today can be moved by a targeting rule instead of a deploy. boolean, string, number and object, each taking a fallback.

    It is the one accessor in this package that does not throw on a missing binding, and that is the design rather than an oversight. Everywhere else a missing binding means the request cannot be served either way. A flag decides between two paths that both work, so a Worker that cannot reach Flagship takes the path it took before flags existed. The fallback therefore covers all three failures — no binding, a failed evaluation, a type mismatch — and an evaluation error is logged and swallowed, the way AnalyticsBinding.writeSafe treats a failed telemetry write.

    Which makes the fallback the most important argument in the call, and worth saying plainly: it is the deployed behaviour, not boilerplate. Pass what the Worker used before the flag existed. resolve(env) is still inherited and still throws, for a caller that genuinely cannot proceed without a live evaluation.

  • FlagshipSettings, carrying the binding name plus a context merged into every evaluation, with a flagship module in cloudflareDefaults and CloudflareConfiguration. FLAGS?: Flagship joins CloudflareEnv.

    The context is the deployment-wide half of a targeting rule — service name, environment, region — so no call site has to restate what the deployment already knows about itself. A call's own context wins on any key both name. The app the flags come from is app_id in wrangler.json rather than a setting, so a Worker reading two apps declares two bindings.

    Only the four value methods are exposed. The platform's *Details variants carry variant and reason for experiment analysis, while the untyped get returns unknown. Both stay reachable through resolve(env).

  • AiSearchBinding — managed retrieval over one indexed corpus. search returns passages and generates nothing; chat does both and returns the chunks its answer came from, which is what makes a citation possible. info and stats answer "is the corpus behind?", which a search response cannot.

    Streaming is a separate chatStream rather than stream: true on chat, because the two return different things — a parsed response and a ReadableStream — and a boolean argument that changes a return type is a discriminated union pretending to be an option.

  • AiSearchNamespaceBinding — the same product bound to a namespace instead of an instance, so a request names the corpus. instance, list, create, remove, and a multi-instance search.

    instance() is synchronous and hands back the platform's own AiSearchInstance, so items, jobs and the rest stay reachable with this package out of the way. The multi-instance search reports partial failure rather than hiding it: chunks are tagged with their instance and errors names the ones that did not answer.

    create from the request path is unusual, and it is there because the namespace binding's reason to exist is the case where it is not: a corpus created when a customer signs up cannot be declared in wrangler.json.

  • AiSearchSettings and AiSearchNamespaceSettings, with aiSearch and aiSearchNamespace modules in cloudflareDefaults and CloudflareConfiguration. AI_SEARCH?: AiSearchInstance and AI_SEARCH_NAMESPACE?: AiSearchNamespace join CloudflareEnv.

    The two defaults deliberately differ, because a Worker may declare both and two bindings cannot share a name on one env. AiSearchSettings.options carries retrieval defaults — result count, score floor, reranking — applied all-or-nothing, the same rule Workflows' retention follows. The namespace settings carry only a binding name: the instance is named per call, and a multi-instance search requires instance_ids, which is the question being asked.

  • PipelineBinding — the producer side of Cloudflare Pipelines, which takes structured records and lands them in R2 as batched files on rules configured on the pipeline rather than by the Worker sending to it.

    Two ways to send, and choosing between them is the whole decision. send throws like every other accessor here; sendSafe logs and returns false, exactly as AnalyticsBinding.writeSafe does. A pipeline's usual job is a second, independent sink beside the one doing the real work, and a sink like that failing the run that produced the records inverts its purpose — the archive exists to explain a bad run and would instead be causing one. An audit trail that is the only copy should still throw.

    send resolves when the pipeline has accepted the records, not when they are written. An empty array is passed through rather than short-circuited, because whether an empty batch is worth a call belongs to the pipeline.

  • PipelineSettings, carrying a binding name and nothing else, with a pipeline module in cloudflareDefaults and CloudflareConfiguration. PIPELINE?: Pipeline joins CloudflareEnv.

    Nothing to default, and that is the point: batching, destination, file format and partitioning are configured on the pipeline and apply to every producer. Moving that policy off the producer is most of the reason to send through a pipeline instead of writing to R2 directly.

  • VpcServiceBinding — one service on a private network, reached over a Cloudflare Tunnel. fetch and nothing else, over Binding<Fetcher>, since a VPC Service binding is Fetcher structurally.

    The destination is service_id in wrangler.json and fixed at deploy time. That is the property worth understanding rather than working around: a Worker holding this binding can reach exactly one private endpoint, and no request it handles can talk it into reaching another.

  • VpcNetworkBinding — a whole private network, by Cloudflare Tunnel or Cloudflare Mesh, with each call naming its own destination. fetch for HTTP and connect for raw TCP — Redis, Memcached, MQTT — plaintext only at the time of writing.

    Prefer a VPC Service wherever one endpoint will do. This binding reaches anything on the network, so an address derived from an incoming request is a server-side request forgery with a private network behind it.

  • VpcServiceSettings, VpcNetworkSettings and VpcNetwork, with vpcService and vpcNetwork modules in cloudflareDefaults and CloudflareConfiguration. VPC_SERVICE?: Fetcher and VPC_NETWORK?: VpcNetwork join CloudflareEnv.

    Both settings carry a binding name and nothing else. There is deliberately no allowlist of reachable destinations: a security control enforced in library code is one a caller routes around by calling resolve(env), and Cloudflare's own answer is a VPC Service per destination.

🔧 Enhancements

  • CloudflareEnv now describes every binding this package can reach. It previously named ten of eighteen, which made the missing ones easy to mistake for unsupported resources rather than gaps in the package. AI Search accounts for two of them, since it is two bindings.

    PIPELINE?: Pipeline is the one member typed from a module rather than the ambient scope, so CloudflareEnv.ts now carries the file’s first import type.

  • The package's own inventory is accurate again. src/index.ts, the README and the bindings guide had all said "ten accessors" and "twelve settings shapes" since 1.0.0. They now say eighteen and twenty, and the bindings guide's table lists Browser Rendering, Workflows, Flagship, Pipelines and both AI Search bindings with their wrangler.json keys like every other row.

  • The bindings guide no longer teaches a class that exists. Its "adding a binding of your own" walkthrough used a hypothetical BrowserBinding with a screenshot method — which as of this release is a real class without that method. The walkthrough now builds an EmailBinding, which this package genuinely does not provide.

🐛 Bug Fixes

  • None. No prior behaviour changed.

🔐 Security

  • No new dependency, which is the substance of the design. Driving a browser — launching, navigating, evaluating — needs @cloudflare/puppeteer, a real runtime package rather than a type. Taking it on here would put a browser automation library in the dependency tree of every Worker that installs this package to get a KV accessor.

    Workflows and Flagship needed no such decision: Workflow, WorkflowInstance, Flagship and FlagshipEvaluationContext are all in @cloudflare/workers-types, so those accessors offer their full surface for free. The difference is why one of these three classes has no methods and the other two do.

    dependencies therefore still holds @bayudwiyansatria/core alone, and peerDependencies still holds @cloudflare/workers-types with an optional hono.

🧪 Tests

npm run lint, npm run test:run, npm run build, and npm run build:docs all clean. 176 specs across 17 suites, up from 109 across nine.

test/core/bindings/BrowserBinding.spec.ts pins the part that was missing before the class existed: an absent binding reported as a MissingBindingError naming BROWSER, rather than undefined handed onward to fail elsewhere. It also covers the settings override and both degradation paths, isBound and tryResolve.

test/core/bindings/WorkflowBinding.spec.ts covers the three things this accessor actually decides: that create puts the payload under params where the platform expects it and omits the key entirely for a Workflow that takes none, that a caller-supplied id survives — the only idempotency Workflows offers — and both directions of the retention merge. The file reaches 100% of the class.

test/core/bindings/FlagshipBinding.spec.ts spends most of its length on the fallback, because a fallback that only works in one of the three failure modes is worse than none — it covers an absent binding and a throwing evaluation, and asserts the value is passed to the platform as well, which is what covers the third. The rest pins the context merge in both directions. Also 100% of the class.

The two AI Search specs concentrate on the few places those classes decide anything of their own rather than passing through: the all-or-nothing options merge in both directions, that chat overrides a caller who set stream themselves so the return type stays honest, and that the two default binding names differ. Both files reach 100%.

test/core/bindings/PipelineBinding.spec.ts spends its length on the difference between the two send methods, since that is the only decision this class offers: send propagates both a missing binding and a rejected batch, and sendSafe answers false for either without raising. Also 100%.

The packaging check for Pipelines is not in the suite and could not be — the specs import from src/, so a mis-bundled lib/ would pass them. lib/index.d.ts was read directly to confirm the module import survived as an import, and the minified bundles to confirm they carry no trace of it.

📚 Documentation

  • All eight new classes document why they have no service beside them, rather than leaving the asymmetry to be discovered. The reasons differ, which is the point of saying them out loud: BrowserBinding would need a dependency this package will not take, while WorkflowBinding and FlagshipBinding have no kernel capability to implement — neither durable execution, flag evaluation, managed retrieval, record archival nor private-network access is among the abstractions @bayudwiyansatria/core names, and inventing one for a single implementation with no consumers would be ceremony rather than abstraction. Flagship is the likeliest of the eight to earn one later, since flag evaluation has many providers and none of its vocabulary is Cloudflare's.

  • docs/reference/bindings.md gains a Workflows, a Browser Rendering, a Flagship, an AI Search and a Pipelines section, each with its wrangler.json shape. The Flagship one leads with the degradation posture, since an accessor that answers instead of throwing is the surprise in a guide where twelve others throw. The Workflows one states what the accessor deliberately does not do: it is the producer side, the WorkflowEntrypoint holding the steps stays in the consuming Worker, and createBatch does not chunk on the caller's behalf because the right chunk size depends on payload size. The AI Search one leads with which of the two bindings to declare, since that is the whole decision, and closes by placing the product against Vectorize — both do retrieval, and the difference is whether a Worker owns the embedding pipeline. The Pipelines one does the same against R2 and Queues: writing to R2 directly means the producer owns buffering, and a queue exists so another Worker can act on each message where a pipeline exists so nothing has to.

⬆️ Upgrading

npm install @bayudwiyansatria/cloudflare@1.3.0

First, check whether your Env declares any of the new binding names — BROWSER, WORKFLOW, FLAGS, PIPELINE, AI_SEARCH, AI_SEARCH_NAMESPACE, VPC_SERVICE, VPC_NETWORK. If it does, delete the local declaration and let the inherited one stand:

- import type { BrowserWorker } from '@cloudflare/puppeteer'
  import type { CloudflareEnv } from '@bayudwiyansatria/cloudflare'

  export interface Env extends CloudflareEnv {
-   BROWSER?: BrowserWorker
  }

Fetcher is assignable to BrowserWorker, so a call site passing the binding into puppeteer.launch keeps compiling untouched. See ⚠️ Breaking Changes for why this is necessary.

Otherwise nothing to change. To adopt the Browser accessor, replace a direct env.BROWSER read:

const browser = new BrowserBinding()

if (!browser.isBound(env)) {
  return fail('This deployment cannot render pages')
}

const session = await puppeteer.launch(browser.resolve(env))

To start a Workflow:

const workflow = new WorkflowBinding<DataRequest>()

const run = await workflow.create(env, { source: 'upload', objectKey })
const state = await workflow.status(env, run.id)

To move a vars entry behind a flag without changing what an unflagged deployment does:

const flags = new FlagshipBinding()

const channel = await flags.string(env, 'release-channel', env.RELEASE_CHANNEL ?? 'stable')

The vars entry stays where it is and becomes the fallback, so a deployment with no Flagship binding — and a deployment whose flag service is unreachable — behaves exactly as it does today.

The binding name is configurable through configure() like every other module, so a Worker whose wrangler.json calls it something else no longer has to hard-code that name at the call site.

🚨 Known Issues

  • No BrowserService. Deliberate — see 🔐 Security. Holding the binding is all this package can offer without taking a dependency, and the driving has one consumer today.

  • No WorkflowService, and no kernel capability behind it. Also deliberate. A Worker consumes WorkflowBinding directly, which means its business code names Cloudflare — the one thing the service layer exists to prevent. That is the right trade only while there is nothing to abstract over; when a second durable-execution provider is in play, or a second consumer disagrees with the first about what the seam should look like, the interface belongs in the kernel and this becomes its adapter.

  • Workflows, Flagship, AI Search, Pipelines and Workers VPC have no known migration requirement. All seven accessors are deliberately available before consumers need direct binding access.

  • Workers VPC is in beta, and this is the one place the package describes a binding shape itself. Cloudflare states that its features and APIs may change before general availability, and @cloudflare/workers-types declares no VPC type at the pinned version, so VpcNetwork is hand-written. A hand-written type cannot be checked against the runtime: if the surface changes before GA this compiles and fails in production. VpcServiceBinding is unaffected — it names Fetcher, a real type. When the official type ships, VpcNetwork should be deleted rather than maintained.

  • Flagship swallows evaluation errors. By design — see 🚀 Features — but it means a misconfigured app ID or a wrong flag key reads as "every flag returned its fallback" rather than as a failure. console.error carries the reason, and isAvailable(env) answers the binding half of it; there is no way to distinguish "flag is off" from "flag could not be read" from the return value alone. A caller that needs to would use resolve(env) and the platform's *Details methods, which report reason.

📦 Dependencies

Kind Package
Runtime @bayudwiyansatria/core
Peer @cloudflare/workers-types
Optional peer hono — only for ./middlewares

Unchanged.

👥 Contributors

  • Bayu Dwiyan Satria

🙏 Acknowledgments

Thanks to the consumer whose direct browser-binding access exposed the missing abstraction. The asymmetry was only visible because the surrounding bindings already followed the accessor pattern.

For more information, visit the project's GitHub repository.

results matching ""

    No results matching ""