Skip to content
workers-go
Esc
navigateopen⌘Jpreview
On this page

Generated bindings (exp/cloudflare)

How the exp/cloudflare Go bindings are mechanically generated from @cloudflare/workers-types.

exp/cloudflare/<pkg> holds Go bindings for Cloudflare Workers runtime APIs that are mechanically generated from the @cloudflare/workers-types TypeScript definitions. Each subpackage wraps one JS interface/class family: one JS interface or class becomes one Go type, one method becomes one Go method, one property becomes one Go getter (or struct field, for data types).

Prefer the hand-written packages under cloudflare/ (kv, r2, d1, cache, queues, sockets, …) where one already exists for the API you need — some of them (kv, r2, cache, and the producer side of queues) wrap a generated package here and round out its rough edges (e.g. cloudflare/kv returns a proper kv.ErrNotFound where the generated KVNamespace.GetText would otherwise hand back a nil *string). Use exp/cloudflare/<pkg> directly for APIs with no hand-written package yet, or reach into a generated handle’s JSValue() when a hand-written wrapper doesn’t expose something you need.

How it fits together

node_modules/@cloudflare/workers-types/<date>/index.d.ts   (not committed)
        │  scripts/gen-bindings/src/extract.ts (Node + TypeScript Compiler API)

scripts/gen-bindings/ir/index.json                          (committed IR)
scripts/gen-bindings/ir/SOURCE                              (package@version + extraction date)
        │  scripts/gen-bindings/cfgen (Go) + exp/internal/gen/overrides/<pkg>.yaml

exp/cloudflare/<pkg>/z<pkg>_gen.go                           (generated, DO NOT EDIT)
exp/cloudflare/<pkg>/<pkg>.go                                (hand-written, only where needed)
  • scripts/gen-bindings/ir/index.json is a JSON dump of every top-level declaration (interface, class, type alias) in the workers-types .d.ts source, extracted once and committed. It’s package/version-tagged by the sibling SOURCE file.
  • scripts/gen-bindings/cfgen (a separate Go module, so its yaml dependency doesn’t leak into this module’s go.sum) reads that IR plus one overrides YAML file per output package, and writes exp/cloudflare/<pkg>/z<pkg>_gen.go. It only needs Go — no Node — so this step, and the -check mode used in CI, run without any JS toolchain.
  • exp/internal/jsrt is the runtime helper vocabulary the generated code calls into (jsrt.Await, jsrt.Call, jsrt.Binding, byte/typed-array/Date/Headers conversions, …).
  • A package may also contain a hand-written <pkg>.go file for anything cfgen can’t express mechanically — a synchronous method bridged to another package’s types (hyperdrive.Hyperdrive.Connect), a Go type hosting a JS-side class (durableobjects.Register, workflows.Register), or an API with no IR declaration at all (cf.FromRequest).

exp/cloudflare/doc.go defines WorkersTypesVersion, the exact @cloudflare/workers-types version the checked-in IR (and therefore every generated package) was derived from.

Type mapping

The generator maps each TypeScript shape to a Go equivalent:

TypeScript Go
string / boolean / number string / bool / float64 (or int via a types: override)
Promise<T> a method return becomes (T, error)
T | null / T | undefined on a nullable primitive *T
T | null / T | undefined where T is itself a nested data-type struct *T (omitted from toJS() when nil)
Date time.Time
ReadableStream<...> io.ReadCloser
ArrayBuffer / Uint8Array []byte
Array<T> []T
Record<string, T> map[string]T
Map<string, T> map[string]T (a JS Map, unlike a Record, isn’t a plain object)
Headers http.Header
a ref to another type in the same package’s include: list that type’s Go type
anything else unresolvable (Request/Response, an unincluded ref, most unions/intersections/functions) js.Value — the escape hatch: call .JSValue() on any generated handle type, or use a value directly, when the wrapper doesn’t expose what you need

See exp/cloudflare/README.md’s “Type mapping” table for the complete set of rules, including how inline object/union types are synthesized into their own named Go structs.

Every generated binding-backed type follows the same constructor convention: New<Name>(bindingName string) (*Name, error) (or (Name, error) for a data type) resolves the binding from the Worker’s env at call time — the same pattern kv.NewNamespace, r2.NewBucket, and every other package in this section use.

Hand-written packages wrapping a generated one

cloudflare/kv, cloudflare/r2, cloudflare/cache, and the producer side of cloudflare/queues are all “L2” packages: each holds a handle to a generated “L1” type (e.g. kv.Namespace wraps a *kvjs.KVNamespace from exp/cloudflare/kv), builds the right options struct for each call, and adapts the result to a narrower, pre-existing Go API. cloudflare/dostub.go (DurableObjectNamespace/DurableObjectStub) is a variant of the same pattern: it predates exp/cloudflare/durableobjects and keeps its own pre-existing exported API, but delegates internally to the generated durableobjects package.

Regenerating the bindings

Most contributors never need to do this — the IR and all generated files are committed. Regenerate only when a newer @cloudflare/workers-types release should be picked up, or when adding/changing a generated package’s overrides.

Requirements: Node.js 24+ and pnpm (for the extraction step only — no ts-node/tsx needed, since it uses Node’s built-in TypeScript type stripping), and Go 1.21+ for cfgen itself.

make gen-bindings        # pnpm install + extract -> IR, then cfgen -> Go

This is also the sole //go:generate directive in exp/cloudflare/doc.go, so go generate ./exp/cloudflare/... (from the repo root) works too. To regenerate only one package while iterating on its overrides file:

go run -C scripts/gen-bindings ./cfgen -root "$(pwd)" -pkg workflows

CI runs make gen-bindings-check, which re-derives every package from the committed IR and overrides and fails if the checked-in z<pkg>_gen.go files don’t match — it doesn’t need Node/pnpm, since it only reads the already-committed IR.

Adding a new generated package

  1. Find the declaration name(s) you need in the committed IR: jq '.decls[] | select(.name | test("MyThing")) | .name' scripts/gen-bindings/ir/index.json, then inspect the full declaration before writing overrides.
  2. Add exp/internal/gen/overrides/mypkg.yaml (package name = output directory name) with at least package: and include: — the declarations to generate.
  3. Run go run -C scripts/gen-bindings ./cfgen -root "$(pwd)" -pkg mypkg and read any warnings — each one names a field/param that fell back to js.Value and why.
  4. Add types:/rename:/handwritten:/exclude: entries to tighten the mapping, and re-run.
  5. GOOS=js GOARCH=wasm go build ./exp/... and go vet to confirm the generated package compiles, then add at least one //go:build js && wasm test.
  6. make gen-bindings-check should now pass.

See exp/cloudflare/README.md’s “Writing an overrides file” section for the full YAML schema (bindings:, rename:, types:, overloads:, handwritten:, exclude:, …).

Was this page helpful?