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
CloudflareEnvgaining 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 CloudflareEnvand 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 }BrowserWorkeris{ fetch: typeof fetch }— narrower than theFetcherthatCloudflareEnvnow declares — so upgrading producedTS2430: Interface Env incorrectly extends interface CloudflareEnv, plus five cascadingTS2345/TS2344errors everywhere thatEnvwas passed where aCloudflareEnvwas 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_SERVICEandVPC_NETWORK. A Worker that declared none of them is unaffected.CloudflareConfigurationgains eight required members:aiSearch,aiSearchNamespace,browser,flagship,pipeline,vpcNetwork,vpcServiceandworkflow. That is source-compatible for every consumer building its configuration fromcloudflareDefaults, 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, overBindinglike the other ten. Defaults to theBROWSERbinding name, takes aBrowserSettingsoverride, and inheritsresolve,tryResolve,isBound, andname.BrowserSettings, aBindingSettingscarrying the binding name and nothing else, plus abrowsermodule incloudflareDefaultsandCloudflareConfiguration.BROWSER?: FetcherjoinsCloudflareEnv.Fetcherrather 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, overBindinglike the rest.create,createBatchandgetkeep the platform's own names so Cloudflare's documentation still describes this class, andstatus(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,WorkflowInstanceand their options all come from@cloudflare/workers-types, already a peer.WorkflowSettings, carrying the binding name plussuccessRetentionanderrorRetention, with aworkflowmodule incloudflareDefaultsandCloudflareConfiguration.WORKFLOW?: WorkflowjoinsCloudflareEnv.Retention is settings rather than a call argument for the same reason KV's
expirationTtlis: 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 namesretentionitself 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 avarsentry today can be moved by a targeting rule instead of a deploy.boolean,string,numberandobject, 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.writeSafetreats 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 acontextmerged into every evaluation, with aflagshipmodule incloudflareDefaultsandCloudflareConfiguration.FLAGS?: FlagshipjoinsCloudflareEnv.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_idinwrangler.jsonrather than a setting, so a Worker reading two apps declares two bindings.Only the four value methods are exposed. The platform's
*Detailsvariants carryvariantandreasonfor experiment analysis, while the untypedgetreturnsunknown. Both stay reachable throughresolve(env).AiSearchBinding— managed retrieval over one indexed corpus.searchreturns passages and generates nothing;chatdoes both and returns thechunksits answer came from, which is what makes a citation possible.infoandstatsanswer "is the corpus behind?", which a search response cannot.Streaming is a separate
chatStreamrather thanstream: trueonchat, because the two return different things — a parsed response and aReadableStream— 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-instancesearch.instance()is synchronous and hands back the platform's ownAiSearchInstance, soitems,jobsand the rest stay reachable with this package out of the way. The multi-instancesearchreports partial failure rather than hiding it: chunks are tagged with their instance anderrorsnames the ones that did not answer.createfrom 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 inwrangler.json.AiSearchSettingsandAiSearchNamespaceSettings, withaiSearchandaiSearchNamespacemodules incloudflareDefaultsandCloudflareConfiguration.AI_SEARCH?: AiSearchInstanceandAI_SEARCH_NAMESPACE?: AiSearchNamespacejoinCloudflareEnv.The two defaults deliberately differ, because a Worker may declare both and two bindings cannot share a name on one
env.AiSearchSettings.optionscarries 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 requiresinstance_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.
sendthrows like every other accessor here;sendSafelogs and returnsfalse, exactly asAnalyticsBinding.writeSafedoes. 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.sendresolves 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 apipelinemodule incloudflareDefaultsandCloudflareConfiguration.PIPELINE?: PipelinejoinsCloudflareEnv.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.fetchand nothing else, overBinding<Fetcher>, since a VPC Service binding isFetcherstructurally.The destination is
service_idinwrangler.jsonand 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.fetchfor HTTP andconnectfor 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,VpcNetworkSettingsandVpcNetwork, withvpcServiceandvpcNetworkmodules incloudflareDefaultsandCloudflareConfiguration.VPC_SERVICE?: FetcherandVPC_NETWORK?: VpcNetworkjoinCloudflareEnv.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
CloudflareEnvnow 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?: Pipelineis the one member typed from a module rather than the ambient scope, soCloudflareEnv.tsnow carries the file’s firstimport 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 theirwrangler.jsonkeys 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
BrowserBindingwith ascreenshotmethod — which as of this release is a real class without that method. The walkthrough now builds anEmailBinding, 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,FlagshipandFlagshipEvaluationContextare 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.dependenciestherefore still holds@bayudwiyansatria/corealone, andpeerDependenciesstill holds@cloudflare/workers-typeswith an optionalhono.
🧪 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:
BrowserBindingwould need a dependency this package will not take, whileWorkflowBindingandFlagshipBindinghave no kernel capability to implement — neither durable execution, flag evaluation, managed retrieval, record archival nor private-network access is among the abstractions@bayudwiyansatria/corenames, 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.mdgains a Workflows, a Browser Rendering, a Flagship, an AI Search and a Pipelines section, each with itswrangler.jsonshape. 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, theWorkflowEntrypointholding the steps stays in the consuming Worker, andcreateBatchdoes 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 consumesWorkflowBindingdirectly, 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-typesdeclares no VPC type at the pinned version, soVpcNetworkis hand-written. A hand-written type cannot be checked against the runtime: if the surface changes before GA this compiles and fails in production.VpcServiceBindingis unaffected — it namesFetcher, a real type. When the official type ships,VpcNetworkshould 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.errorcarries the reason, andisAvailable(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 useresolve(env)and the platform's*Detailsmethods, which reportreason.
📦 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.