`adapter-cloudflare`: wrangler.json `vars` not available via `$env/dynamic/private` during dev
Nobody has claimed this yet.
- #15627 by @teemingc — closed without merging
- Dominant language
- JavaScript
- Stars
- 20.8k
- Forks
- 2.3k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 156
Description
Describe the bug
When using @sveltejs/adapter-cloudflare with a wrangler.json file, environment variables defined under vars are only available via platform.env during local development. They are not bridged into process.env, which means $env/dynamic/private cannot see them.
This creates a split where:
varsinwrangler.json→ available viaplatform.env(local + production), invisible to$env/dynamic/privateduring dev- values in
.env→ available via$env/dynamic/privateduring dev (Vite loads them intoprocess.env), not available at runtime on Cloudflare
In production on Cloudflare, $env/dynamic/private correctly resolves both vars and secrets from the Worker runtime. The inconsistency is local-dev only, but it forces developers into one of these workarounds:
- Duplicate every var in both
wrangler.jsonand.env, keeping them manually in sync. - Abandon
$env/dynamic/privateand access everything throughevent.platform.env, losing the ergonomics of SvelteKit's env module and requiring null-safety guards sinceplatformcan be undefined. - Write a custom Vite plugin that reads
wrangler.jsonand injectsvarsintoprocess.envat dev time.
None of these are obvious, and developers hit this wall only after migrating away from $env/static/private (which is the recommended move on Cloudflare Pages — see context below).
Context: why developers end up here
On Cloudflare Pages, adding a wrangler.json to your project causes the dashboard-defined plaintext environment variables to no longer be injected into the build container's process.env. This means $env/static/private breaks at build time for any variable not defined as an encrypted secret.
The natural fix is to:
- Move plaintext config into
wrangler.jsonvars(the intended source of truth for runtime config) - Switch from
$env/static/privateto$env/dynamic/private(since vars are now runtime bindings) - Keep secrets in
.env/.dev.varsfor local dev and in the Cloudflare dashboard for production
This is the correct architecture — build once, resolve at runtime. But step 2 silently breaks local dev because the adapter doesn't bridge wrangler.json vars into the env system that $env/dynamic/private reads from.
Suggested approach
In the adapter's Vite plugin (or dev hook), read the resolved wrangler config and merge vars into process.env with lower precedence than existing values:
This keeps wrangler.json as the single source of truth for runtime config, .env as the source for local secrets, and $env/dynamic/private works everywhere without duplication or custom plugins.
Reproduction
- Create a SvelteKit project with
@sveltejs/adapter-cloudflare - Define a var in
wrangler.json:
{
"name": "my-app",
"pages_build_output_dir": ".svelte-kit/cloudflare",
"vars": {
"API_HOST": "https://api.example.com"
}
}
- In a server endpoint or hook, import and log it:
import { env } from '$env/dynamic/private';
console.log(env.API_HOST); // undefined during `vite dev`
- Run
npm run dev—env.API_HOSTisundefined - Add
API_HOST=https://api.example.comto.env— now it works, but you have the value in two places
Expected behavior
During local dev, $env/dynamic/private should include variables defined in wrangler.json vars. The adapter already reads the wrangler config (via platformProxy.configPath) to populate platform.env — it should also inject those values into process.env so that SvelteKit's own env module works consistently.
Values from .env / .dev.vars should take precedence over wrangler.json vars to allow local secret overrides.
Logs
System Info
@sveltejs/adapter-cloudflare 7.2.8
Severity
annoyance
Additional Information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the adapter's Vite plugin and the platformProxy.configPath handling described in the issue. Reproduce the behavior with wrangler.json and npm run dev, then verify that $env/dynamic/private receives vars while .env and .dev.vars values retain precedence. Done means local development matches the expected runtime environment behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- cloud
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 39/100