Context (ctx)
The Context API provides methods to manage the lifecycle of your Worker or Durable Object.
Context is exposed via the following places:
- As the third parameter in all handlers, including the
fetch()
handler. (fetch(request, env, ctx)
) - As a class property of the
WorkerEntrypoint
class (this.ctx
)
Note that the Context API is available strictly in stateless contexts, that is, not Durable Objects. However, Durable Objects have a different object, the Durable Object State, which is available as this.ctx
inside a Durable Object class, and provides some of the same functionality as the Context API.
ctx.props
provides a way to pass additional configuration to a worker based on the context in which it was invoked. For example, when your Worker is called by another Worker, ctx.props
can provide information about the calling worker.
For example, imagine that you are configuring a Worker called "frontend-worker", which must talk to another Worker called "doc-worker" in order to manipulate documents. You might configure "frontend-worker" with a Service Binding like:
{ "services": [ { "binding": "DOC_SERVICE", "service": "doc-worker", "entrypoint": "DocServiceApi", "props": { "clientId": "frontend-worker", "permissions": [ "read", "write" ] } } ]}
[[services]]binding = "DOC_SERVICE"service = "doc-worker"entrypoint = "DocServiceApi"props = { clientId = "frontend-worker", permissions = ["read", "write"] }
Now frontend-worker can make calls to doc-worker with code like env.DOC_SERVICE.getDoc(id)
. This will make a Remote Procedure Call invoking the method getDoc()
of the class DocServiceApi
, a WorkerEntrypoint
class exported by doc-worker.
The configuration contains a props
value. This in an arbitrary JSON value. When the DOC_SERVICE
binding is used, the DocServiceApi
instance receiving the call will be able to access this props
value as this.ctx.props
. Here, we've configured props
to specify that the call comes from frontend-worker, and that it should be allowed to read and write documents. However, the contents of props
can be anything you want.
The Workers platform is designed to ensure that ctx.props
can only be set by someone who has permission to edit and deploy the worker to which it is being delivered. This means that you can trust that the content of ctx.props
is authentic. There is no need to use secret keys or cryptographic signatures in a ctx.props
value.
ctx.props
can also be used to configure an RPC interface to represent a specific resource, thus creating a "custom binding". For example, we could configure a Service Binding to our "doc-worker" which grants access only to a specific document:
{ "services": [ { "binding": "FOO_DOCUMENT", "service": "doc-worker", "entrypoint": "DocumentApi", "props": { "docId": "e366592caec1d88dff724f74136b58b5", "permissions": [ "read", "write" ] } } ]}
[[services]]binding = "FOO_DOCUMENT"service = "doc-worker"entrypoint = "DocumentApi"props = { docId = "e366592caec1d88dff724f74136b58b5", permissions = ["read", "write"] }
Here, we've placed a docId
property in ctx.props
. The DocumentApi
class could be designed to provide an API to the specific document identified by ctx.props.docId
, and enforcing the given permissions.
ctx.exports
provides automatically-configured "loopback" bindings for all of your top-level exports.
- For each top-level export that
extends WorkerEntrypoint
(or simply implements a fetch handler),ctx.exports
automatically contains a Service Binding. - For each top-level export that
extends DurableObject
(and which has been configured with storage via a migration),ctx.exports
automatically contains a Durable Object namespace binding.
For example:
import { WorkerEntrypoint } from "cloudflare:workers";
export class Greeter extends WorkerEntrypoint { greet(name) { return `Hello, ${name}!`; }}
export default { async fetch(request, env, ctx) { let greeting = await ctx.exports.Greeter.greet("World") return new Response(greeting); }}
In this example, the default fetch handler calls the Greeter
class over RPC, like how you'd use a Service Binding. However, there is no external configuration required. ctx.exports
is populated automatically from your top-level imports.
Loopback Service Bindings in ctx.exports
have an extra capability that regular Service Bindings do not: the caller can specify the value of ctx.props
that should be delivered to the callee.
import { WorkerEntrypoint } from "cloudflare:workers";
export class Greeter extends WorkerEntrypoint { greet(name) { return `${this.ctx.props.greeting}, ${name}!`; }}
export default { async fetch(request, env, ctx) { // Make a custom greeter that uses the greeting "Welcome". let greeter = ctx.exports.Greeter({props: {greeting: "Welcome"}})
// Greet the world. Returns "Welcome, World!" let greeting = await ctx.exports.Greeter.greet("World")
return new Response(greeting); }}
import { WorkerEntrypoint } from "cloudflare:workers";
type Props = { greeting: string}
export class Greeter extends WorkerEntrypoint<Env, Props> { greet(name) { return `${this.ctx.props.greeting}, ${name}!`; }}
export default { async fetch(request, env, ctx) { // Make a custom greeter that uses the greeting "Welcome". let greeter = ctx.exports.Greeter({props: {greeting: "Welcome"}})
// Greet the world. Returns "Welcome, World!" let greeting = await ctx.exports.Greeter.greet("World")
return new Response(greeting); }} satisfies ExportedHandler<Env>;
Specifying props dynamically is permitted in this case because the caller is the same Worker, and thus can be presumed to be trusted to specify any props. The ability to customize props is particularly useful when the resulting binding is to be passed to another Worker over RPC or used in the env
of a dynamically-loaded worker.
Note that props
values specified in this way are allowed to contain any "persistently" serializable type. This includes all basic structured clonable data types ↗. It also includes Service Bindings themselves: you can place a Service Binding into the props
of another Service Binding.
If using TypeScript, you should use the wrangler types
command to auto-generate types for your project. The generated types will ensure ctx.exports
is typed correctly.
When declaring an entrypoint class that accepts props
, make sure to declare it as extends WorkerEntrypoint<Env, Props>
, where Props
is the type of ctx.props
. See the example above.
ctx.waitUntil()
extends the lifetime of your Worker, allowing you to perform work without blocking returning a response, and that may continue after a response is returned. It accepts a Promise
, which the Workers runtime will continue executing, even after a response has been returned by the Worker's handler.
waitUntil
is commonly used to:
- Fire off events to external analytics providers. (note that when you use Workers Analytics Engine, you do not need to use
waitUntil
) - Put items into cache using the Cache API
You can call waitUntil()
multiple times. Similar to Promise.allSettled
, even if a promise passed to one waitUntil
call is rejected, promises passed to other waitUntil()
calls will still continue to execute.
For example:
export default { async fetch(request, env, ctx) { // Forward / proxy original request let res = await fetch(request);
// Add custom header(s) res = new Response(res.body, res); res.headers.set('x-foo', 'bar');
// Cache the response // NOTE: Does NOT block / wait ctx.waitUntil(caches.default.put(request, res.clone()));
// Done return res; },};
The passThroughOnException
method allows a Worker to fail open ↗, and pass a request through to an origin server when a Worker throws an unhandled exception. This can be useful when using Workers as a layer in front of an existing service, allowing the service behind the Worker to handle any unexpected error cases that arise in your Worker.
export default { async fetch(request, env, ctx) { // Proxy to origin on unhandled/uncaught exceptions ctx.passThroughOnException(); throw new Error('Oops'); },};
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark