Skip to content

Commit e139f06

Browse files
[wrangler] Add CF_PAGES environment variables to pages dev
Injects Pages-specific environment variables (CF_PAGES, CF_PAGES_BRANCH, CF_PAGES_COMMIT_SHA, CF_PAGES_URL) during local development with wrangler pages dev. This enables frameworks like SvelteKit to auto-detect the Pages environment during local development, improving dev/prod parity. The variables can be overridden by user-defined vars in wrangler.toml, .dev.vars, or via CLI flags. Fixes #1440
1 parent d49dcb5 commit e139f06

7 files changed

Lines changed: 151 additions & 8 deletions

File tree

.changeset/cf-pages-env-vars.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add CF_PAGES environment variables to `wrangler pages dev`
6+
7+
`wrangler pages dev` now automatically injects Pages-specific environment variables (`CF_PAGES`, `CF_PAGES_BRANCH`, `CF_PAGES_COMMIT_SHA`, `CF_PAGES_URL`) for improved dev/prod parity. This enables frameworks like SvelteKit to auto-detect the Pages environment during local development.
8+
9+
- `CF_PAGES` is set to `"1"` to indicate the Pages environment
10+
- `CF_PAGES_BRANCH` defaults to the current git branch (or `"local"` if not in a git repo)
11+
- `CF_PAGES_COMMIT_SHA` defaults to the current git commit SHA (or a placeholder if not in a git repo)
12+
- `CF_PAGES_URL` is set to the local dev server URL
13+
14+
These variables can be overridden by user-defined vars in `wrangler.toml`, `.dev.vars`, or via CLI flags.

packages/wrangler/e2e/pages-dev.test.ts

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,15 @@ describe.sequential("wrangler pages dev", () => {
124124
).flat();
125125
expect(bindings).toMatchInlineSnapshot(`
126126
[
127-
"env.TEST_DO (TestDurableObject, defined in a) Durable Object local [not connected]",
128-
"env.TEST_KV (TEST_KV) KV Namespace local",
129-
"env.TEST_D1 (local-TEST_D1) D1 Database local",
130-
"env.TEST_R2 (TEST_R2) R2 Bucket local",
131-
"env.TEST_SERVICE (test-worker) Worker local [not connected]",
127+
"env.TEST_DO (TestDurableObject, defined in a) Durable Object local [not connected]",
128+
"env.TEST_KV (TEST_KV) KV Namespace local",
129+
"env.TEST_D1 (local-TEST_D1) D1 Database local",
130+
"env.TEST_R2 (TEST_R2) R2 Bucket local",
131+
"env.TEST_SERVICE (test-worker) Worker local [not connected]",
132+
"env.CF_PAGES ("(hidden)") Environment Variable local",
133+
"env.CF_PAGES_BRANCH ("(hidden)") Environment Variable local",
134+
"env.CF_PAGES_COMMIT_SHA ("(hidden)") Environment Variable local",
135+
"env.CF_PAGES_URL ("(hidden)") Environment Variable local",
132136
]
133137
`);
134138
});
@@ -325,9 +329,13 @@ describe.sequential("wrangler pages dev", () => {
325329
expect(normalizeOutput(worker.currentOutput)).toMatchInlineSnapshot(`
326330
"✨ Compiled Worker successfully
327331
Your Worker has access to the following bindings:
328-
Binding Resource Mode
329-
env.KV_BINDING_TOML (KV_ID_TOML) KV Namespace local
330-
env.PAGES ("⚡️ Pages ⚡️") Environment Variable local
332+
Binding Resource Mode
333+
env.KV_BINDING_TOML (KV_ID_TOML) KV Namespace local
334+
env.CF_PAGES ("(hidden)") Environment Variable local
335+
env.CF_PAGES_BRANCH ("(hidden)") Environment Variable local
336+
env.CF_PAGES_COMMIT_SHA ("(hidden)") Environment Variable local
337+
env.CF_PAGES_URL ("(hidden)") Environment Variable local
338+
env.PAGES ("⚡️ Pages ⚡️") Environment Variable local
331339
⎔ Starting local server...
332340
[wrangler:info] Ready on http://<HOST>:<PORT>
333341
[wrangler:info] GET / 200 OK (TIMINGS)"
@@ -445,6 +453,10 @@ describe.sequential("wrangler pages dev", () => {
445453
env.SERVICE_BINDING_1_TOML (NEW_SERVICE_NAME_1) Worker local [not connected]
446454
env.SERVICE_BINDING_2_TOML (SERVICE_NAME_2_TOML) Worker local [not connected]
447455
env.SERVICE_BINDING_3_TOML (SERVICE_NAME_3_ARGS) Worker local [not connected]
456+
env.CF_PAGES ("(hidden)") Environment Variable local
457+
env.CF_PAGES_BRANCH ("(hidden)") Environment Variable local
458+
env.CF_PAGES_COMMIT_SHA ("(hidden)") Environment Variable local
459+
env.CF_PAGES_URL ("(hidden)") Environment Variable local
448460
env.VAR1 ("(hidden)") Environment Variable local
449461
env.VAR2 ("VAR_2_TOML") Environment Variable local
450462
env.VAR3 ("(hidden)") Environment Variable local
@@ -481,6 +493,62 @@ describe.sequential("wrangler pages dev", () => {
481493
);
482494
});
483495

496+
it("should inject CF_PAGES environment variables", async () => {
497+
const helper = new WranglerE2ETestHelper();
498+
await helper.seed({
499+
"_worker.js": dedent`
500+
export default {
501+
fetch(request, env) {
502+
return Response.json({
503+
CF_PAGES: env.CF_PAGES,
504+
CF_PAGES_BRANCH: env.CF_PAGES_BRANCH,
505+
CF_PAGES_COMMIT_SHA: env.CF_PAGES_COMMIT_SHA,
506+
CF_PAGES_URL: env.CF_PAGES_URL,
507+
});
508+
}
509+
}`,
510+
});
511+
const worker = helper.runLongLived(
512+
`${cmd} --port ${port} --inspector-port ${inspectorPort} . --compatibility-date=2024-01-01`
513+
);
514+
const { url } = await worker.waitForReady();
515+
516+
const response = await fetch(url);
517+
const data = (await response.json()) as Record<string, string>;
518+
519+
expect(data.CF_PAGES).toBe("1");
520+
expect(data.CF_PAGES_BRANCH).toBeDefined();
521+
expect(data.CF_PAGES_COMMIT_SHA).toBeDefined();
522+
expect(data.CF_PAGES_URL).toContain("http");
523+
});
524+
525+
it("should allow user to override CF_PAGES environment variables", async () => {
526+
const helper = new WranglerE2ETestHelper();
527+
await helper.seed({
528+
"_worker.js": dedent`
529+
export default {
530+
fetch(request, env) {
531+
return new Response(env.CF_PAGES_BRANCH);
532+
}
533+
}`,
534+
"wrangler.toml": dedent`
535+
name = "test-pages"
536+
pages_build_output_dir = "."
537+
compatibility_date = "2024-01-01"
538+
539+
[vars]
540+
CF_PAGES_BRANCH = "custom-branch"
541+
`,
542+
});
543+
const worker = helper.runLongLived(
544+
`${cmd} --port ${port} --inspector-port ${inspectorPort}`
545+
);
546+
const { url } = await worker.waitForReady();
547+
548+
const text = await fetchText(url);
549+
expect(text).toBe("custom-branch");
550+
});
551+
484552
describe("watch mode", () => {
485553
it("should modify worker during dev session (Functions)", async () => {
486554
const helper = new WranglerE2ETestHelper();

packages/wrangler/src/api/startDevWorker/ConfigController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ async function resolveBindings(
191191
input.envFiles,
192192
!input.dev?.remote,
193193
{
194+
defaultVars: input.defaultVars,
194195
kv: extractBindingsOfType("kv_namespace", input.bindings),
195196
vars: Object.fromEntries(
196197
extractBindingsOfType("plain_text", input.bindings).map((b) => [

packages/wrangler/src/api/startDevWorker/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ export interface StartDevWorkerInput {
104104

105105
/** The bindings available to the worker. The specified bindind type will be exposed to the worker on the `env` object under the same key. */
106106
bindings?: Record<string, Binding>; // Type level constraint for bindings not sharing names
107+
/**
108+
* Default vars that can be overridden by config vars.
109+
* Useful for injecting environment-specific defaults like CF_PAGES variables.
110+
*/
111+
defaultVars?: Record<string, string>;
107112
migrations?: DurableObjectMigration[];
108113
containers?: ContainerApp[];
109114
/** The triggers which will cause the worker's exported default handlers to be called. */

packages/wrangler/src/dev.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ export const dev = createCommand({
310310
});
311311

312312
export type AdditionalDevProps = {
313+
/**
314+
* Default vars that can be overridden by config vars.
315+
* Useful for injecting environment-specific defaults like CF_PAGES variables.
316+
*/
317+
defaultVars?: Record<string, string>;
313318
vars?: Record<string, string | Json>;
314319
kv?: {
315320
binding: string;
@@ -643,6 +648,9 @@ export function getBindings(
643648

644649
// non-inheritable fields
645650
vars: {
651+
// defaultVars provide baseline values (e.g., CF_PAGES vars for Pages dev)
652+
// that can be overridden by config vars, .dev.vars, and CLI args
653+
...args.defaultVars,
646654
// Use a copy of combinedVars since we're modifying it later
647655
...getVarsForDev(
648656
configParam.userConfigPath,

packages/wrangler/src/dev/start-dev.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ async function setupDevEnv(
253253
assets: undefined,
254254
}),
255255
},
256+
defaultVars: args.defaultVars,
256257
dev: {
257258
auth,
258259
remote: args.enablePagesAssetsServiceBinding

packages/wrangler/src/pages/dev.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,44 @@ const DEFAULT_IP = process.platform === "win32" ? "127.0.0.1" : "localhost";
8282
const DEFAULT_PAGES_LOCAL_PORT = 8788;
8383
const DEFAULT_SCRIPT_PATH = "_worker.js";
8484

85+
/**
86+
* Generates Pages-specific environment variables for local development.
87+
* These variables are normally injected by Cloudflare Pages CI during builds/deployments.
88+
* @see https://developers.cloudflare.com/pages/configuration/build-configuration/#environment-variables
89+
*/
90+
function getPagesEnvironmentVariables(
91+
localUrl: string
92+
): Record<string, string> {
93+
let branch = "local";
94+
let commitSha = "0000000000000000000000000000000000000000";
95+
96+
// Attempt to get actual git info for more realistic mocking
97+
try {
98+
branch = execSync("git rev-parse --abbrev-ref HEAD", {
99+
encoding: "utf-8",
100+
stdio: ["pipe", "pipe", "pipe"],
101+
}).trim();
102+
} catch {
103+
// Not a git repo or git not available, use default
104+
}
105+
106+
try {
107+
commitSha = execSync("git rev-parse HEAD", {
108+
encoding: "utf-8",
109+
stdio: ["pipe", "pipe", "pipe"],
110+
}).trim();
111+
} catch {
112+
// Not a git repo or git not available, use default
113+
}
114+
115+
return {
116+
CF_PAGES: "1",
117+
CF_PAGES_BRANCH: branch,
118+
CF_PAGES_COMMIT_SHA: commitSha,
119+
CF_PAGES_URL: localUrl,
120+
};
121+
}
122+
85123
export const pagesDevCommand = createCommand({
86124
metadata: {
87125
description: "Develop your full-stack Pages application locally",
@@ -873,6 +911,11 @@ export const pagesDevCommand = createCommand({
873911
}
874912
}
875913

914+
// Get CF_PAGES environment variables for local dev
915+
const pagesEnvVars = getPagesEnvironmentVariables(
916+
`${localProtocol ?? "http"}://${ip}:${port}`
917+
);
918+
876919
const devServer = await run(
877920
{
878921
MULTIWORKER: Array.isArray(args.config),
@@ -927,6 +970,9 @@ export const pagesDevCommand = createCommand({
927970
compatibilityDate,
928971
compatibilityFlags,
929972
nodeCompat: undefined,
973+
// CF_PAGES vars as defaults (can be overridden by config vars)
974+
defaultVars: pagesEnvVars,
975+
// CLI vars override everything
930976
vars,
931977
kv: kv_namespaces,
932978
durableObjects: do_bindings,

0 commit comments

Comments
 (0)