Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/content/changelog/workers/2025-03-14-importable-env.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Import `env` for direct access to bindings in your Worker code
description: More easily configure your Worker and call bindings from anywhere with an importable `env`
products:
- workers
date: 2025-03-14T15:00:00Z
---

import { Render, TypeScriptExample } from "~/components";

You can now access [bindings](/workers/runtime-apis/bindings/)
from anywhere in your Worker by importing the `env` object from `cloudflare:workers`.

Previously, `env` could only be accessed during a request. This meant that
bindings could not be used in the top-level context of a Worker.

Now, you can import `env` and access bindings such as [secrets](/workers/configuration/secrets/)
or [environment variables](/workers/configuration/environment-variables/) in the
initial setup for your Worker:

```js
import { env } from "cloudflare:workers";
import ApiClient from "example-api-client";

// API_KEY and LOG_LEVEL now usable in top-level scope
const apiClient = ApiClient.new({ apiKey: env.API_KEY });
const LOG_LEVEL = env.LOG_LEVEL || "info";

export default {
fetch(req) {
// you can use apiClient or LOG_LEVEL, configured before any request is handled
},
};
```

:::note
Workers do not allow I/O from outside a request context. This means that even
though `env` is accessible from the top-level scope, you will not be able to access
every binding's methods.

For instance, environment variables and secrets are accessible, and you are able to
call `env.NAMESPACE.get` to get a [Durable Object stub](/durable-objects/api/stub/) in the
top-level context. However, calling methods on the Durable Object stub, making [calls to a KV store](/kv/api/),
and [calling to other Workers](/workers/runtime-apis/bindings/service-bindings) will not work.
:::

Additionally, `env` was normally accessed as a argument to a Worker's entrypoint handler,
such as [`fetch`](/workers/runtime-apis/fetch/).
This meant that if you needed to access a binding from a deeply nested function,
you had to pass `env` as an argument through many functions to get it to the
right spot. This could be cumbersome in complex codebases.

Now, you can access the bindings from anywhere in your codebase
without passing `env` as an argument:

```js
// helpers.js
import { env } from "cloudflare:workers";

// env is *not* an argument to this function
export async function getValue(key) {
let prefix = env.KV_PREFIX;
return await env.KV.get(`${prefix}-${key}`);
}
```

For more information, see [documentation on accessing `env`](/workers/runtime-apis/bindings#how-to-access-env).
174 changes: 157 additions & 17 deletions src/content/docs/workers/runtime-apis/bindings/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ pcx_content_type: concept
title: Bindings (env)
head: []
description: Worker Bindings that allow for interaction with other Cloudflare Resources.

---

import { DirectoryListing, WranglerConfig } from "~/components"
import { DirectoryListing, WranglerConfig } from "~/components";

Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform. Bindings provide better performance and less restrictions when accessing resources from Workers than the [REST APIs](/api/) which are intended for non-Workers applications.

Expand All @@ -31,12 +30,12 @@ r2_buckets = [

```js
export default {
async fetch(request, env) {
const key = url.pathname.slice(1);
await env.MY_BUCKET.put(key, request.body);
return new Response(`Put ${key} successfully!`);
}
}
async fetch(request, env) {
const key = url.pathname.slice(1);
await env.MY_BUCKET.put(key, request.body);
return new Response(`Put ${key} successfully!`);
},
};
```

You can think of a binding as a permission and an API in one piece. With bindings, you never have to add secret keys or tokens to your Worker in order to access resources on your Cloudflare account — the permission is embedded within the API itself. The underlying secret is never exposed to your Worker's code, and therefore can't be accidentally leaked.
Expand All @@ -51,12 +50,12 @@ The following is a good approach:

```ts
export default {
fetch(request, env) {
let client = new Client(env.MY_SECRET); // `client` is guaranteed to be up-to-date with the latest value of `env.MY_SECRET` since a new instance is constructed with every incoming request
fetch(request, env) {
let client = new Client(env.MY_SECRET); // `client` is guaranteed to be up-to-date with the latest value of `env.MY_SECRET` since a new instance is constructed with every incoming request

// ... do things with `client`
}
}
// ... do things with `client`
},
};
```

Compared to this alternative, which might have surprising and unwanted behavior:
Expand All @@ -65,12 +64,153 @@ Compared to this alternative, which might have surprising and unwanted behavior:
let client = undefined;

export default {
fetch(request, env) {
client ??= new Client(env.MY_SECRET); // `client` here might not be updated when `env.MY_SECRET` changes, since it may already exist in global scope
fetch(request, env) {
client ??= new Client(env.MY_SECRET); // `client` here might not be updated when `env.MY_SECRET` changes, since it may already exist in global scope

// ... do things with `client`
},
};
```

If you have more advanced needs, explore the [AsyncLocalStorage API](/workers/runtime-apis/nodejs/asynclocalstorage/), which provides a mechanism for exposing values down to child execution handlers.

## How to access `env`

Bindings are located on the `env` object, which can be accessed in several ways:

- It is an argument to entrypoint handlers such as [`fetch`](/workers/runtime-apis/fetch/):

```js
export default {
async fetch(request, env) {
return new Response(`Hi, ${env.NAME}`);
},
};
```

// ... do things with `client`
* It is as class property on [WorkerEntrypoint](/workers/runtime-apis/bindings/service-bindings/rpc/#bindings-env),
[DurableObject](/durable-objects/), and [Workflow](/workflows/):

```js
export class MyDurableObject extends DurableObject {
async sayHello() {
return `Hi, ${this.env.NAME}!`;
}
}
```

* It can be imported from `cloudflare:workers`:

```js
import { env } from "cloudflare:workers";
console.log(`Hi, ${this.env.Name}`);
```

### Importing `env`

Importing `env` from `cloudflare:workers` is useful when you need to access a binding
such as [secrets](/workers/configuration/secrets/) or [environment variables](/workers/configuration/environment-variables/)
to do initial Worker configuration.

```js
import { env } from "cloudflare:workers";
import ApiClient from "example-api-client";

// API_KEY and LOG_LEVEL now usable in top-level scope
let apiClient = ApiClient.new({ apiKey: env.API_KEY });
const LOG_LEVEL = env.LOG_LEVEL || "info";

export default {
fetch(req) {
// you can use apiClient or LOG_LEVEL, configured before any request is handled
},
};
```

Workers do not allow I/O from outside a request context. This means that even
though `env` is accessible from the top-level scope, you will not be able to access
every binding's methods.

For instance, environment variables and secrets are accessible, and you are able to
call `env.NAMESPACE.get` to get a [Durable Object stub](/durable-objects/api/stub/) in the
top-level context. However, calling methods on the Durable Object stub, making [calls to a KV store](/kv/api/),
and [calling to other Workers](/workers/runtime-apis/bindings/service-bindings) will not work.

```js
import { env } from "cloudflare:workers";

// This would error!
// env.KV.get('my-key')

export default {
async fetch(req) {
// This works
let myVal = await env.KV.get("my-key");
Response.new(myVal);
},
};
```

Additionally, importing `env` from `cloudflare:workers` lets you avoid passing `env`
as an argument through many function calls if you need to access a binding from a deeply-nested
function. This can be helpful in a complex codebase.

```js
import { env } from "cloudflare:workers";

export default {
fetch(req) {
Response.new(sayHello());
},
};

// env is not an argument to sayHello...
function sayHello() {
let myName = getName();
return `Hello, ${myName}`;
}

// ...nor is it an argument to getName
function getName() {
return env.MY_NAME;
}
```

If you have more advanced needs, explore the [AsyncLocalStorage API](/workers/runtime-apis/nodejs/asynclocalstorage/), which provides a mechanism for exposing values down to child execution handlers.
:::note
While using `env` from `cloudflare:workers` may be simpler to write than passing it
through a series of function calls, passing `env` as an argument is a helpful pattern
for dependency injection and testing.
:::

### Overriding `env` values

The `withEnv` function provides a mechanism for overriding values of `env`.

Imagine a user has defined the [environment variable](/workers/configuration/environment-variables/)
"NAME" to be "Alice" in their Wrangler configuration file and deployed a Worker. By default, logging
`env.NAME` would print "Alice". Using the `withEnv` function, you can override the value of
"NAME".

```js
import { env, withEnv } from "cloudflare:workers";

function logName() {
console.log(env.NAME);
}

export default {
fetch(req) {
// this will log "Alice"
logName();

withEnv({ NAME: "Bob" }, () => {
// this will log "Bob"
logName();
});

// ...etc...
},
};
```

This can be useful when testing code that relies on an imported `env` object.
Loading