---
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 provides a robust solution for managing environment variables in Cloudflare Workers, offering validation, type safety, and security features that go beyond Cloudflare's built-in environment variable and secrets handling.

Both approaches below use `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"]
}
```
:::

## Two approaches

1. **Using `varlock-wrangler`** - A thin wrapper for `wrangler` that injects env vars correctly, along with a one-line init import in your worker
2. **With the Vite plugin** - If you're already using the [Cloudflare Workers Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/), you can add the varlock Cloudflare Vite plugin to automatically inject env vars and init code

:::note[SvelteKit on Cloudflare]
Deploying SvelteKit via `@sveltejs/adapter-cloudflare`? Use `varlockSvelteKitCloudflarePlugin` from `@varlock/cloudflare-integration/sveltekit` — a separate entry point that skips `@cloudflare/vite-plugin` (which isn't SvelteKit-compatible) and uses an SSR-entry-based env loader instead. The [SvelteKit guide](/integrations/sveltekit/#sveltekit-on-cloudflare-workers) walks through the setup end-to-end.
:::

## 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 dedicated [`varlockSvelteKitCloudflarePlugin`](/integrations/sveltekit/#sveltekit-on-cloudflare-workers) from `@varlock/cloudflare-integration/sveltekit` instead.

### 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](#two-approaches) 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 — 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, 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>




## 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 by design** — Your `.env.schema` gives AI coding tools full context on your config (names, types, descriptions) without ever exposing secret values.
