---
title: Cloudflare Workers
description: How to integrate varlock with Cloudflare Workers and Wrangler for secure, type-safe environment management
---
import { Steps } from "@astrojs/starlight/components";
import Badge from '@/components/Badge.astro';
import ExecCommandWidget from '@/components/ExecCommandWidget.astro';
import InstallJsDepsWidget from '@/components/InstallJsDepsWidget.astro';

<div class="page-badges">
  <Badge npmPackage="@varlock/cloudflare-integration" />
</div>

Varlock manages environment variables in Cloudflare Workers with validation, type safety, and security features that go beyond Cloudflare's built-in environment variable and secrets handling.

Our integration relies on using `varlock-wrangler`, a thin wrapper around `wrangler` that automatically resolves and uploads your env vars as Cloudflare secrets and vars during deployment, and injects them into miniflare during local dev.

:::caution[Requires `nodejs_compat`]
Varlock relies on a handful of `node:*` built-ins at runtime, so your worker must opt into Cloudflare's Node.js compatibility layer. Add `nodejs_compat` to your `compatibility_flags` in `wrangler.toml` / `wrangler.json`. Without it, the worker will fail to start with "Cannot resolve 'node:...'" errors.

```json title="wrangler.json"
{
  "compatibility_flags": ["nodejs_compat"]
}
```
:::

## Choosing an approach

All paths use the same `.env.schema` and `varlock-wrangler deploy` for production uploads. The difference is how env reaches your worker during **local dev** and **build**:

| Approach | Best when | Dev / build |
|----------|-----------|-------------|
| **[`varlock-wrangler`](#approach-1-using-varlock-wrangler)** | Plain Workers, minimal tooling | `varlock-wrangler dev` injects env via FIFO and watches `.env` files; `varlock-wrangler deploy` uploads vars + secrets |
| **[Vite plugin](#approach-2-with-the-vite-plugin)** | Already using [`@cloudflare/vite-plugin`](https://developers.cloudflare.com/workers/vite-plugin/) | `vite dev` injects into miniflare automatically; `vite build` + `varlock-wrangler deploy` |
| **[Astro adapter](/integrations/astro/#deploying-to-cloudflare-workers)** | Astro with [`@astrojs/cloudflare`](https://docs.astro.build/en/guides/integrations-guide/cloudflare/) | `@varlock/astro-integration` auto-detects the adapter; `astro dev` works as-is; `astro build` + `varlock-wrangler deploy` |
| **[SvelteKit](/integrations/sveltekit/#setup-for-cloudflare-workers)** | SvelteKit with [`@sveltejs/adapter-cloudflare`](https://svelte.dev/docs/kit/adapter-cloudflare) | `@varlock/vite-integration` auto-detects the adapter; `vite dev` via an SSR entry loader; `vite build` + `varlock-wrangler deploy` |

:::caution[Using SvelteKit? Follow the SvelteKit guide instead]
This page covers plain Workers and the `@cloudflare/vite-plugin` flow, which **does not support SvelteKit** ([workers-sdk#8922](https://github.com/cloudflare/workers-sdk/issues/8922)). For SvelteKit with `@sveltejs/adapter-cloudflare`, set up varlock using the **[SvelteKit → Cloudflare Workers guide](/integrations/sveltekit/#setup-for-cloudflare-workers)**. You use the standard `varlockVitePlugin` (which auto-detects the adapter), **not** `varlockCloudflareVitePlugin`. You'll still install `@varlock/cloudflare-integration` (for `varlock-wrangler`) and deploy the same way described below.
:::

## Approach 1: Using `varlock-wrangler`

Replace your `wrangler` commands with `varlock-wrangler` and initialize varlock in your worker code with a single import. It's a thin wrapper that wires up your env vars correctly and passes everything else through unchanged.

### Setup

<Steps>

1. **Install packages**
    <InstallJsDepsWidget packages="@varlock/cloudflare-integration varlock" />

1. **Run `varlock init` to set up your `.env.schema` file**

    This will guide you through setting up your `.env.schema` file, based on your existing `.env` file(s). Make sure to review it carefully.

    <ExecCommandWidget command="varlock init" showBinary={false} />

1. **Add the varlock init import to your worker entry point**

    This initializes varlock's `ENV` proxy and applies console redaction and response leak detection (unless disabled):

    ```diff lang="ts" title="src/index.ts"
    +import '@varlock/cloudflare-integration/init';
    +import { ENV } from 'varlock/env';

    export default {
      async fetch(request, env, ctx) {
    +    // both work:
    +    console.log(ENV.MY_VAR);  // varlock (recommended)
    +    console.log(env.MY_VAR);  // native Cloudflare
        return new Response('Hello!');
      },
    };
    ```

1. **Update your package.json scripts to use `varlock-wrangler`**

    Use `varlock-wrangler` as a drop-in replacement for `wrangler`:

    ```diff lang="json" title="package.json" add="varlock-"
    {
      "scripts": {
        "dev": "varlock-wrangler dev",
        "deploy": "varlock-wrangler deploy",
        "types": "varlock-wrangler types",
      }
    }
    ```

    If you deploy via [Cloudflare Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) instead of a local/CI script, override the deploy command in your Cloudflare dashboard under **Settings → Build → Deploy command** (see [Workers Builds](#cloudflare-workers-builds-cicd) below).

</Steps>

### How it works

- **In dev**: `varlock-wrangler dev` resolves your env and injects it into `wrangler` using `--env-file` with a named pipe (unix/mac) or temp file (windows). It also watches your `.env` files and automatically restarts wrangler when they change, something `wrangler dev` doesn't do.
- **In production**: `varlock-wrangler deploy` (and `varlock-wrangler versions upload`) attaches non-sensitive values as Cloudflare vars (via `--var`) and sensitive values as Cloudflare secrets via `--secrets-file`. The worker reads them at runtime via `import { env } from 'cloudflare:workers'`.

----

## Approach 2: With the Vite plugin

If you're building with Vite, `varlockCloudflareVitePlugin` wraps the [Cloudflare Workers Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/) and adds env var + varlock init injection. No extra init import needed, and both `ENV.MY_VAR` and native `env.MY_VAR` work in dev and production.

For SvelteKit on Cloudflare, use the standard [`varlockVitePlugin`](/integrations/sveltekit/#setup-for-cloudflare-workers) instead. It auto-detects the Cloudflare adapter and wires up the SSR entry loader.

### Setup

<Steps>

1. **Install packages**
    <InstallJsDepsWidget packages="@varlock/cloudflare-integration @cloudflare/vite-plugin varlock" />

    `@cloudflare/vite-plugin` is an (optional) peer dependency. This plugin wraps it, so you need it installed in the same project. SvelteKit users should omit it (see the [SvelteKit callout](#choosing-an-approach) at the top of this page).

1. **Run `varlock init` to set up your `.env.schema` file**

    <ExecCommandWidget command="varlock init" showBinary={false} />

1. **Update your Vite config**

    Replace the `cloudflare()` plugin with `varlockCloudflareVitePlugin()`. It is a thin wrapper of the Cloudflare vite plugin and passes through all config:

    ```diff lang="ts" title="vite.config.ts"
    import { defineConfig } from 'vite';
    -import { cloudflare } from '@cloudflare/vite-plugin';
    +import { varlockCloudflareVitePlugin } from '@varlock/cloudflare-integration';

    export default defineConfig({
      plugins: [
    -    cloudflare(),
    +    varlockCloudflareVitePlugin(),
        // other plugins ...
      ],
    });
    ```

    Any options you were passing to `cloudflare()` can be passed directly to `varlockCloudflareVitePlugin()` at the top level.

1. **Deploy with `varlock-wrangler`**

    Use `varlock-wrangler deploy` instead of `wrangler deploy` in your deploy script:

    ```diff lang="json" title="package.json" add="varlock-"
    {
      "scripts": {
        "dev": "vite dev",
        "build": "vite build",
        "deploy": "npm run build && varlock-wrangler deploy"
      }
    }
    ```

    If you deploy via [Cloudflare Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) instead of a local/CI script, override the deploy command in your Cloudflare dashboard under **Settings → Build → Deploy command** (see [Workers Builds](#cloudflare-workers-builds-cicd) below).

</Steps>

:::caution[Remove `.dev.vars`]
If you have a `.dev.vars` file in your project root, **delete it**. Varlock manages env injection automatically. A `.dev.vars` file will conflict and the plugin will throw an error. This also applies to `.dev.vars.<environment>` files.
:::

### How it works

- **In dev**: Resolved vars are automatically injected into miniflare's bindings.
- **In production**: Non-sensitive values are statically replaced at build time by vite. `varlock-wrangler deploy` sets non-sensitive values as Cloudflare vars and sensitive values as secrets.

### Upgrading from `@varlock/vite-integration`

If you were previously using `varlockVitePlugin()` from `@varlock/vite-integration` with Cloudflare Workers:

1. Install `@varlock/cloudflare-integration` (you can remove `@varlock/vite-integration`, it's included)
2. In `vite.config.ts`, replace both `varlockVitePlugin()` and `cloudflare()` with `varlockCloudflareVitePlugin()`
3. Replace `wrangler deploy` with `varlock-wrangler deploy` in your deploy script

```diff lang="ts" title="vite.config.ts"
-import { varlockVitePlugin } from '@varlock/vite-integration';
-import { cloudflare } from '@cloudflare/vite-plugin';
+import { varlockCloudflareVitePlugin } from '@varlock/cloudflare-integration';

export default defineConfig({
  plugins: [
-    varlockVitePlugin(),
-    cloudflare(),
+    varlockCloudflareVitePlugin(),
  ],
});
```

Your secrets will no longer be bundled into the JS artifact. They'll be stored in Cloudflare's secret management instead.

----

## `varlock-wrangler` CLI

`varlock-wrangler` is a thin wrapper around `wrangler`. It enhances the commands below; everything else (`tail`, `secret list`, etc.) is passed through unchanged, so you can use `varlock-wrangler` everywhere or just for these commands.

<div class="reference-docs">
<div>
### `varlock-wrangler dev`

Wraps `wrangler dev` with automatic env injection. Resolves your environment variables, injects them into miniflare, and watches your `.env` files to restart wrangler when they change.
</div>

<div>
### `varlock-wrangler deploy` / `versions upload`

Resolves your environment variables from `.env.schema` + `.env` files, then uploads non-sensitive values as Cloudflare vars (`--var`), sensitive values as Cloudflare secrets (`--secrets-file`), and includes a `__VARLOCK_ENV` secret containing the full resolved env graph for the varlock runtime.

:::note[Secure secret handling]
On Unix, secrets are passed to wrangler commands via a named pipe (FIFO) and never written to disk. On Windows, a temporary file is used and immediately deleted after the deploy completes.
:::

:::caution[Varlock owns your env vars]
`varlock-wrangler deploy` replaces **all** Cloudflare vars and secrets with the ones defined in your `.env.schema`. Any vars or secrets set manually via the Cloudflare dashboard or `wrangler secret put` that aren't in your schema will be removed on the next deploy. If you need additional bindings (KV, D1, etc.), configure them in your `wrangler.toml` as usual. Only plain vars/secrets are affected.
:::
</div>

<div>
### `varlock-wrangler types`

Generates Cloudflare Worker types (the `Env` interface) including all varlock-managed environment variables, so your `env.MY_VAR` access is properly typed alongside other Cloudflare bindings (KV, D1, etc.).
</div>
</div>

----

## Log redaction and leak prevention

Both integration approaches automatically enable **log redaction** (sensitive values are masked in console output) and **response leak detection** (responses are scanned for accidentally exposed secrets). These are enabled by default and can be disabled in your `.env.schema` using root decorators:

```env-spec title=".env.schema"
# @redactLogs=false
# @preventLeaks=false
```

For more details, see the [root decorators reference](/reference/root-decorators/).

----

## Managing Multiple Environments

Varlock can load multiple _environment-specific_ `.env` files (e.g., `.env.development`, `.env.preview`, `.env.production`) by using the [`@currentEnv` root decorator](/reference/root-decorators/#currentenv).

The simplest approach is to use the built-in [`VARLOCK_ENV`](/reference/builtin-variables/#varlock_env) variable, which auto-detects the deployment environment on most CI platforms, including [Cloudflare Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) (via `WORKERS_CI_BRANCH`), Vercel, Netlify, and others. Locally it resolves to `development`, and during tests to `test`.

```env-spec title=".env.schema"
# @currentEnv=$VARLOCK_ENV
# ---
```

If you need more control, for example mapping specific branches to specific environments, you can define your own env-selection var instead:

```env-spec title=".env.schema"
# @currentEnv=$APP_ENV
# ---
WORKERS_CI_BRANCH=
# @type=enum(development, preview, production, test)
APP_ENV=remap($WORKERS_CI_BRANCH, "main", production, /.*/, preview, undefined, development)
```

For more information, see the [environments guide](/guides/environments).

----

## Cloudflare Workers Builds (CI/CD)

If you deploy via [Cloudflare Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/), two pieces of setup are required in the dashboard, and neither can be configured from a file in the repo:

<Steps>

1. **Override the Deploy command**

    Under **Settings → Build → Deploy command**, replace the default `npx wrangler deploy` with:

    ```sh
    npx varlock-wrangler deploy
    ```

    Without this override, Cloudflare runs stock `wrangler deploy`, which skips varlock's env resolution and leaves your worker without its resolved vars/secrets at runtime.

1. **Set any _secret-zero_ vars under Build variables**

    Any env vars that varlock itself needs _during load_ (for example a 1Password service account token or a GCP key) must be set under **Settings → Build → Variables and Secrets**. These are made available to the build step, where `varlock-wrangler deploy` resolves your full env graph and uploads the result to the worker runtime as regular vars/secrets.

    Vars that only your worker needs at runtime (not varlock itself) don't need to be set here. They'll be resolved by varlock and set automatically by `varlock-wrangler deploy`.

</Steps>

----

## Deploying from your own CI

Deploying from **your own CI** (GitHub Actions, GitLab CI, a self-hosted runner) instead of [Cloudflare Workers Builds](#cloudflare-workers-builds-cicd) works the same way, and isn't really Cloudflare-specific. The same _secret-zero_ rule applies: any var varlock needs to resolve your secrets (a 1Password service account token, a GCP key, etc.) must be present in the job environment before `varlock-wrangler deploy` runs.

```yaml title=".github/workflows/deploy-worker.yml"
jobs:
  deploy:
    permissions:
      id-token: write   # required for OIDC-backed plugins
      contents: read
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npm run build
      - run: npx varlock-wrangler deploy
        env:
          # Only vars varlock needs to *resolve* secrets, not every worker var
          OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
```

For supported providers, varlock can authenticate with **short-lived OIDC tokens** from your CI instead of long-lived keys. See the [OIDC workload identity guide](/guides/oidc/). Run `varlock load` in an earlier step to fail fast on schema errors without exposing values.

----

## Multi-worker and auxiliary Workers

Cloudflare supports running [multiple Workers in one dev session](https://developers.cloudflare.com/workers/development-testing/multi-workers/): a primary Worker plus **auxiliary Workers** reachable via service bindings. The Vite plugin exposes this through [`auxiliaryWorkers`](https://developers.cloudflare.com/workers/development-testing/multi-workers/#vite-plugin-with-auxiliary-workers) in `vite.config.ts`.

:::caution[Single-worker env injection today]
Varlock currently resolves and uploads env vars for **one Wrangler config per deploy**. `varlock-wrangler deploy` injects vars/secrets into the worker named by the target `wrangler.toml` / `wrangler.json`. It does not automatically propagate env to auxiliary Workers or sibling configs passed to `wrangler dev -c ./a.json -c ./b.json`.

For multi-worker repos today:

- Deploy each Worker separately with `varlock-wrangler deploy -c <that-worker-config>`, or
- Share a single `.env.schema` and run deploy once per worker script, or
- Use [`varlock run`](/reference/cli-commands/#run) to inject resolved env when invoking a non-wrangler deploy tool (see [non-wrangler deploy tools](#non-wrangler-deploy-tools-alchemysstpulumi) below).

First-class multi-worker / auxiliary-worker support is not implemented yet. If this blocks your setup, join [Discord](https://chat.dmno.dev) and share your wrangler layout and which Workers need which vars.
:::

----

## When things go wrong

### `Cannot resolve 'node:...'` at worker startup

Your worker is missing the Node.js compatibility layer. Add `nodejs_compat` to `compatibility_flags`; see the **`nodejs_compat` caution** near the top of this page.

### `.dev.vars` conflicts with the Vite plugin

Delete `.dev.vars` and `.dev.vars.<environment>` files when using the Vite plugin or `varlock-wrangler dev`. Varlock owns env injection; leftover Wrangler env files cause hard errors.

### Vars or secrets missing after deploy

- Confirm you deploy with `varlock-wrangler deploy`, not stock `wrangler deploy`, especially on [Workers Builds](#cloudflare-workers-builds-cicd)
- Check that secret-zero CI vars are set for any plugin-backed items varlock must resolve at deploy time
- Remember that `varlock-wrangler deploy` **replaces all** Cloudflare vars and secrets with schema-defined ones. Manually added dashboard secrets not in your schema are removed

### Non-wrangler deploy tools (Alchemy, SST, Pulumi)

If you deploy Workers with **IaC tools other than wrangler** ([Alchemy](https://alchemy.run/), SST, Pulumi, Terraform, etc.), you can still use varlock for **fail-fast validation and secret resolution** via [`varlock run`](/reference/cli-commands/#run), which injects resolved values into `process.env` for the deploy tool to read.

What you **cannot** get today without `varlock-wrangler`: the in-worker runtime layer (`@varlock/cloudflare-integration/init`) that provides console redaction and response leak detection. That layer reads a `__VARLOCK_ENV` binding that only `varlock-wrangler` produces.

A public API to build `__VARLOCK_ENV` bindings for non-wrangler deploy tools is planned but not available yet. Until then, teams on Alchemy/SST/Pulumi typically rely on varlock for validation + env injection at deploy time and skip the in-worker runtime protections.

----

## Varlock vs Cloudflare's built-in env management

Cloudflare Workers have built-in support for [vars](https://developers.cloudflare.com/workers/configuration/environment-variables/) and [secrets](https://developers.cloudflare.com/workers/configuration/secrets/), but managing them across environments quickly becomes painful. Here's what Varlock adds:

- **Single source of truth**: Instead of scattering config across the Cloudflare dashboard, `wrangler.toml`, `.dev.vars`, and your code, everything is defined in one `.env.schema` file that works across local dev, preview, and production.
- **Validation before deploy**: Cloudflare only has basic required var validation. Varlock catches missing vars, wrong types, and invalid values _before_ your worker starts, not when it crashes at runtime.
- **Pull secrets from vaults**: Instead of manually running `wrangler secret put` or copy-pasting values in the dashboard, pull secrets directly from [1Password](/plugins/1password/), [AWS Secrets Manager](/plugins/aws-secrets/), [HashiCorp Vault](/plugins/hashicorp-vault/), and [more](/plugins/overview/).
- **Log redaction & leak prevention**: Cloudflare doesn't prevent you from accidentally `console.log`-ing a secret or including one in a response body. Varlock automatically redacts sensitive values in logs and scans outgoing responses for leaked secrets.
- **Simpler multi-environment setup**: Wrangler's `[env.staging]` / `[env.production]` blocks duplicate config and don't support `.env` file overrides. Varlock uses familiar `.env.production` / `.env.preview` files with a single schema, and auto-detects the current environment in CI.
- **Local dev that matches production**: `.dev.vars` is completely disconnected from your production secrets. With Varlock, local dev resolves from the same schema and same secret sources, so there's no drift between environments.
- **AI-safe**: Your `.env.schema` gives AI coding tools full context on your config (names, types, descriptions) without ever exposing secret values.
