From 6eaf231ccca410f77ce95c023b45af12b74191f2 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 6 Jul 2026 16:39:59 +0200 Subject: [PATCH 1/9] breaking: replace `EnvVarConfig.static` with `EnvVarConfig.availability` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `static: boolean` property on environment variable configs has been replaced by `availability`, which has four possible values: - `'dynamic'` (default, equivalent to the old `static: false`) — the value is read at run time and also validated at build time - `'static'` (equivalent to the old `static: true`) — the value is inlined at build time, enabling dead-code elimination - `'runtime'` — the value is only available at run time and is not validated during the build, so the build succeeds even if the value is absent. This replaces the `building ? v.optional(...) : ...` workaround for secrets and other variables that aren't set during the build. - `'build'` — the value is only available during the build, validated at build time, but not accessible from `$app/env/*` at run time To migrate, replace `static: true` with `availability: 'static'` and `static: false` with `availability: 'dynamic'`. Variables that previously used the `building` workaround can now use `availability: 'runtime'` instead. --- .changeset/env-availability.md | 5 ++ .../70-environment-variables.md | 19 +++--- .../25-build-and-deploy/90-adapter-vercel.md | 2 +- .../test/apps/prerendered/src/env.ts | 2 +- .../adapter-static/test/apps/spa/src/env.ts | 2 +- packages/kit/src/core/adapt/builder.js | 5 +- packages/kit/src/core/env.js | 61 ++++++++++++++----- packages/kit/src/exports/public.d.ts | 14 +++-- packages/kit/src/exports/vite/index.js | 8 ++- packages/kit/test/apps/async/src/env.ts | 8 +-- packages/kit/test/apps/basics/src/env.ts | 14 ++--- packages/kit/test/apps/options-2/src/env.ts | 8 +-- packages/kit/test/apps/options/source/env.ts | 2 +- .../kit/test/prerendering/basics/src/env.ts | 4 +- packages/kit/types/index.d.ts | 14 +++-- 15 files changed, 110 insertions(+), 58 deletions(-) create mode 100644 .changeset/env-availability.md diff --git a/.changeset/env-availability.md b/.changeset/env-availability.md new file mode 100644 index 000000000000..0ac9bfbdf485 --- /dev/null +++ b/.changeset/env-availability.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': major +--- + +breaking: replace `EnvVarConfig.static` with `EnvVarConfig.availability` diff --git a/documentation/docs/20-core-concepts/70-environment-variables.md b/documentation/docs/20-core-concepts/70-environment-variables.md index d785468585c0..11fb093d03d3 100644 --- a/documentation/docs/20-core-concepts/70-environment-variables.md +++ b/documentation/docs/20-core-concepts/70-environment-variables.md @@ -121,27 +121,32 @@ export const variables = defineEnvVars({ }); ``` -If a value is invalid, the app will fail to start (or build). To opt out of one or the other, use [`building`]($app-env#building) from `$app/env` along with a validator that accepts an optional value: +If a value is invalid, the app will fail to start (or build). If the variable is only available at run time (for example a secret that isn't set during the build), use `availability: 'runtime'` so that it is only validated when the app starts: ```ts /// file: src/env.ts import { defineEnvVars } from '@sveltejs/kit/hooks'; -+++import { building } from '$app/env'+++ import * as v from 'valibot'; export const variables = defineEnvVars({ SECRET: { - // optional when building but required when starting the app - +++schema: building ? v.optional(v.string()) : v.string()+++ + // required when the app starts, but not validated during the build + +++availability: 'runtime',+++ + schema: v.string() } }); ``` You can use validators to make values optional, or transform them (such as turning a string into a boolean, or parsing JSON) — see your validation library's documentation to learn how. -### Static variables +### Availability -By default, variables are dynamic. If a variable is configured with `static: true`, it will be inlined into your application code, enabling optimisations like dead-code elimination: +The `availability` property controls when a variable's value is available, and therefore when it is validated. It has four possible values: + +- `'dynamic'` (default) — the value is read from the environment when the app starts, and is also validated at build time. Use this when the value is the same at build time and run time. +- `'static'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. +- `'runtime'` — the value is only available when the app runs. It is validated at run time only, so the build will succeed even if the value is absent. Use this for secrets and other variables that aren't set during the build. +- `'build'` — the value is only available during the build. It is validated at build time, but is not accessible from `$app/env/*` at run time. Use this for variables that only affect the build, such as feature flags that gate what gets compiled. ```ts /// file: src/env.ts @@ -151,7 +156,7 @@ import * as v from 'valibot'; export const variables = defineEnvVars({ SHOW_DEBUG_OVERLAY: { public: true, - +++static: true,+++ + +++availability: 'static',+++ // coerce to true/false schema: v.pipe( diff --git a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md index 29ba141a2535..60bd635430da 100644 --- a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md +++ b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md @@ -181,7 +181,7 @@ export function load() {

This staging environment was deployed from {data.deploymentGitBranch}.

``` -Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend configuring the variable with `static: true` — which will statically replace the variables, enabling optimisations like dead code elimination. +Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend configuring the variable with `availability: 'static'` — which will statically replace the variables, enabling optimisations like dead code elimination. ## Skew protection diff --git a/packages/adapter-static/test/apps/prerendered/src/env.ts b/packages/adapter-static/test/apps/prerendered/src/env.ts index 01d2ca41b971..1cbb33afe642 100644 --- a/packages/adapter-static/test/apps/prerendered/src/env.ts +++ b/packages/adapter-static/test/apps/prerendered/src/env.ts @@ -3,6 +3,6 @@ import { defineEnvVars } from '@sveltejs/kit/hooks'; export const variables = defineEnvVars({ PUBLIC_ANSWER: { public: true, - static: true + availability: 'static' } }); diff --git a/packages/adapter-static/test/apps/spa/src/env.ts b/packages/adapter-static/test/apps/spa/src/env.ts index 70bc639e00bd..a902bb6d3bf5 100644 --- a/packages/adapter-static/test/apps/spa/src/env.ts +++ b/packages/adapter-static/test/apps/spa/src/env.ts @@ -3,6 +3,6 @@ import { defineEnvVars } from '@sveltejs/kit/hooks'; export const variables = defineEnvVars({ PUBLIC_VALUE: { public: true, - static: true + availability: 'static' } }); diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 13d630fd9758..47cfcbea59cd 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -167,7 +167,10 @@ export function create_builder({ const issues = {}; for (const [name, config] of Object.entries(variables)) { - if (config.static || !config.public) continue; + const availability = config.availability ?? 'dynamic'; + // only `dynamic` public vars are included in the prerendered env.js — + // `static` is inlined, `build` isn't exported, and `runtime` isn't available at build time + if (!config.public || availability !== 'dynamic') continue; values[name] = validate(variables, env[name], name, issues); } diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index 3659e49d32aa..4cec21325747 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -99,6 +99,7 @@ export async function load_explicit_env(kit, file, root, mode) { export function create_sveltekit_env(variables, env, entry, is_dev) { const imports = entry ? [ + `import { building } from '$app/env/internal';`, `import { variables } from ${JSON.stringify(entry)};`, `import { validate, handle_issues } from '@sveltejs/kit/internal/env';` ] @@ -106,26 +107,34 @@ export function create_sveltekit_env(variables, env, entry, is_dev) { const declarations = []; const setters = []; + const runtime_setters = []; /** @type {Record} */ const issues = {}; for (const [name, config] of Object.entries(variables ?? {})) { - if (config?.static) { - if (config.public) { + const availability = config?.availability ?? 'dynamic'; + + if (availability === 'static') { + if (config?.public) { const value = validate(variables ?? {}, env[name], name, issues); declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`); } + } else if (availability === 'build') { + // validate at build time, but don't export the value — it isn't available at run time + validate(variables ?? {}, env[name], name, issues); } else { - setters.push( + const target = availability === 'runtime' ? runtime_setters : setters; + + target.push( `const ${name} = validate(variables, env.${name}, ${JSON.stringify(name)}, issues);` ); if (config?.public) { - setters.push(`explicit_public_env.${name} = ${name};`); - setters.push(`rendered_env.${name} = ${name};`); + target.push(`explicit_public_env.${name} = ${name};`); + target.push(`rendered_env.${name} = ${name};`); } else { - setters.push(`dynamic_private_env.${name} = ${name};`); + target.push(`dynamic_private_env.${name} = ${name};`); } } } @@ -146,6 +155,11 @@ export function create_sveltekit_env(variables, env, entry, is_dev) { export function set_env(env) { const issues = {}; ${setters.join('\n')} + ${ + runtime_setters.length > 0 + ? `if (!building) {\n\t\t\t\t\t${runtime_setters.join('\n')}\n\t\t\t\t}` + : '' + } handle_issues(issues); }` ]; @@ -187,9 +201,17 @@ export function create_sveltekit_env_private(variables, env) { for (const [name, config] of Object.entries(variables)) { if (config.public) continue; - const value = config.static - ? devalue.uneval(validate(variables, env[name], name, issues)) - : `env.${name}`; + const availability = config.availability ?? 'dynamic'; + + if (availability === 'build') { + // validated in `create_sveltekit_env`; not exported at run time + continue; + } + + const value = + availability === 'static' + ? devalue.uneval(validate(variables, env[name], name, issues)) + : `env.${name}`; exports.push(`export const ${name} = ${value};\n`); } @@ -219,9 +241,14 @@ export function create_sveltekit_env_public(variables, env, prelude) { for (const [name, config] of Object.entries(variables)) { if (!config.public) continue; - const value = config.static - ? devalue.uneval(validate(variables, env[name], name, issues)) - : `env.${name}`; + const availability = config.availability ?? 'dynamic'; + + if (availability === 'build') continue; + + const value = + availability === 'static' + ? devalue.uneval(validate(variables, env[name], name, issues)) + : `env.${name}`; exports.push(`export const ${name} = ${value};\n`); } @@ -242,9 +269,11 @@ export function create_sveltekit_env_public(variables, env, prelude) { * @param {string} app_dir */ export function create_sveltekit_env_service_worker(variables, env, global, base, app_dir) { - const has_dynamic_public_env = Object.values(variables ?? {}).some( - (config) => config.public && !config.static - ); + const has_dynamic_public_env = Object.values(variables ?? {}).some((config) => { + if (!config.public) return false; + const availability = config.availability ?? 'dynamic'; + return availability === 'dynamic' || availability === 'runtime'; + }); if (!has_dynamic_public_env) { return create_sveltekit_env_service_worker_dev(variables, env, global); @@ -274,6 +303,7 @@ export function create_sveltekit_env_service_worker_dev(variables, env, global) for (const [name, config] of Object.entries(variables ?? {})) { if (!config.public) continue; + if ((config.availability ?? 'dynamic') === 'build') continue; const value = validate(variables ?? {}, env[name], name, issues); properties.push(`${name}: ${devalue.uneval(value)}`); @@ -308,6 +338,7 @@ function create_jsdoc(description) { export function create_explicit_env_types(variables, relative, type) { const declarations = Object.entries(variables) .filter(([_, config]) => !!config.public === (type === 'public')) + .filter(([_, config]) => (config.availability ?? 'dynamic') !== 'build') .map(([name, config]) => { const comment = config.description ? `${create_jsdoc(config.description)}\n` : ''; const type = config.schema diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index e3414cf5e2ce..d8bf72b65d60 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -2405,12 +2405,14 @@ export interface EnvVarConfig { */ public?: boolean; /** - * Whether the value is determined at build time or when the app runs. - * - if `true`, the build time value is inlined into the bundle. This enables optimisations like dead-code elimination - * - if `false`, the value is read from the environment when the app starts - * @default false - */ - static?: boolean; + * When the variable's value is available, and therefore when it is validated. + * - `'static'` — the value is inlined into the bundle at build time, enabling optimisations like dead-code elimination. It is validated at build time. + * - `'dynamic'` — the value is read from the environment when the app starts. It is validated at both build time and run time. + * - `'build'` — the value is only available during the build. It is validated at build time, but is not accessible from `$app/env/*` at run time. + * - `'runtime'` — the value is only available when the app runs. It is validated at run time only, allowing the build to succeed even if the value is absent. + * @default 'dynamic' + */ + availability?: 'static' | 'dynamic' | 'build' | 'runtime'; /** * A [Standard Schema](https://standardschema.dev/) validator that is applied to the value when the app starts. * The validator can output any value — not necessarily a string — but public, non-static values must be diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index d6024634c4c4..e1cf6a07baed 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1602,11 +1602,15 @@ function kit({ svelte_config }) { find_deps(vite_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root); const has_explicit_dynamic_public_env = Object.values(explicit_env_config ?? {}).some( - (variable) => variable.public && !variable.static + (variable) => { + if (!variable.public) return false; + const availability = variable.availability ?? 'dynamic'; + return availability === 'dynamic' || availability === 'runtime'; + } ); // the app only depends on runtime public env if it imports `$app/env/public` - // *and* at least one public env var is actually dynamic (non-static) + // *and* at least one public env var is read at run time (i.e. not static or build) const uses_env_dynamic_public = has_explicit_dynamic_public_env && client_chunks.some( diff --git a/packages/kit/test/apps/async/src/env.ts b/packages/kit/test/apps/async/src/env.ts index 3ebd2aecc14e..f786d83b4a03 100644 --- a/packages/kit/test/apps/async/src/env.ts +++ b/packages/kit/test/apps/async/src/env.ts @@ -3,18 +3,18 @@ import { defineEnvVars } from '@sveltejs/kit/hooks'; export const variables = defineEnvVars({ PRIVATE_STATIC: { public: false, - static: true + availability: 'static' }, PRIVATE_DYNAMIC: { public: false, - static: false + availability: 'dynamic' }, PUBLIC_STATIC: { public: true, - static: true + availability: 'static' }, PUBLIC_DYNAMIC: { public: true, - static: false + availability: 'dynamic' } }); diff --git a/packages/kit/test/apps/basics/src/env.ts b/packages/kit/test/apps/basics/src/env.ts index cdf5d6a8d72b..6787a3758aaf 100644 --- a/packages/kit/test/apps/basics/src/env.ts +++ b/packages/kit/test/apps/basics/src/env.ts @@ -3,30 +3,30 @@ import { defineEnvVars } from '@sveltejs/kit/hooks'; export const variables = defineEnvVars({ PRIVATE_STATIC: { public: false, - static: true + availability: 'static' }, PRIVATE_DYNAMIC: { public: false, - static: false + availability: 'dynamic' }, PUBLIC_STATIC: { public: true, - static: true + availability: 'static' }, PUBLIC_DYNAMIC: { public: true, - static: false + availability: 'dynamic' }, PUBLIC_THEME: { public: true, - static: false + availability: 'dynamic' }, PUBLIC_PRERENDERING: { public: true, - static: false + availability: 'dynamic' }, SOME_JSON: { public: false, - static: true + availability: 'static' } }); diff --git a/packages/kit/test/apps/options-2/src/env.ts b/packages/kit/test/apps/options-2/src/env.ts index d61d03fddfec..429e7483b7ed 100644 --- a/packages/kit/test/apps/options-2/src/env.ts +++ b/packages/kit/test/apps/options-2/src/env.ts @@ -1,21 +1,21 @@ import process from 'node:process'; -import { building } from '$app/env'; import * as v from 'valibot'; export const variables = { MESSAGE: { public: true, description: 'Public env var loaded from the shared test env directory', - static: !process.env.DYNAMIC_PUBLIC_ENV + availability: process.env.DYNAMIC_PUBLIC_ENV ? 'dynamic' : 'static' }, PRIVATE_EXPLICIT_ENV: {}, PRIVATE_STATIC_EXPLICIT_ENV: { - static: true + availability: 'static' }, PRIVATE_VALIDATED_DEFAULT_ENV: { schema: v.optional(v.picklist(['foo', 'bar']), 'foo') }, RUNTIME_ONLY: { - schema: building ? v.optional(v.string()) : v.string() + availability: 'runtime', + schema: v.string() } }; diff --git a/packages/kit/test/apps/options/source/env.ts b/packages/kit/test/apps/options/source/env.ts index 0b49d8f3845b..383286e88abd 100644 --- a/packages/kit/test/apps/options/source/env.ts +++ b/packages/kit/test/apps/options/source/env.ts @@ -5,6 +5,6 @@ export const variables = defineEnvVars({ public: true }, TOP_SECRET_SHH_PLS: { - static: true + availability: 'static' } }); diff --git a/packages/kit/test/prerendering/basics/src/env.ts b/packages/kit/test/prerendering/basics/src/env.ts index e5b56561f084..6905e8c3eacc 100644 --- a/packages/kit/test/prerendering/basics/src/env.ts +++ b/packages/kit/test/prerendering/basics/src/env.ts @@ -3,10 +3,10 @@ import { defineEnvVars } from '@sveltejs/kit/hooks'; export const variables = defineEnvVars({ PUBLIC_STATIC: { public: true, - static: true + availability: 'static' }, PRIVATE_STATIC: { public: false, - static: true + availability: 'static' } }); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 708290b01e93..21df50ceebb2 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2378,12 +2378,14 @@ declare module '@sveltejs/kit' { */ public?: boolean; /** - * Whether the value is determined at build time or when the app runs. - * - if `true`, the build time value is inlined into the bundle. This enables optimisations like dead-code elimination - * - if `false`, the value is read from the environment when the app starts - * @default false - */ - static?: boolean; + * When the variable's value is available, and therefore when it is validated. + * - `'static'` — the value is inlined into the bundle at build time, enabling optimisations like dead-code elimination. It is validated at build time. + * - `'dynamic'` — the value is read from the environment when the app starts. It is validated at both build time and run time. + * - `'build'` — the value is only available during the build. It is validated at build time, but is not accessible from `$app/env/*` at run time. + * - `'runtime'` — the value is only available when the app runs. It is validated at run time only, allowing the build to succeed even if the value is absent. + * @default 'dynamic' + */ + availability?: 'static' | 'dynamic' | 'build' | 'runtime'; /** * A [Standard Schema](https://standardschema.dev/) validator that is applied to the value when the app starts. * The validator can output any value — not necessarily a string — but public, non-static values must be From 6f3057885e69cce8f7b08a76123c5e8ada16a91b Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 7 Jul 2026 23:47:15 +0200 Subject: [PATCH 2/9] adjust --- .../70-environment-variables.md | 10 +++--- .../25-build-and-deploy/90-adapter-vercel.md | 2 +- .../test/apps/prerendered/src/env.ts | 2 +- .../adapter-static/test/apps/spa/src/env.ts | 2 +- packages/kit/src/core/adapt/builder.js | 2 +- packages/kit/src/core/env.js | 36 +++++++++---------- packages/kit/src/exports/public.d.ts | 8 ++--- packages/kit/src/exports/vite/index.js | 2 +- packages/kit/test/apps/async/src/env.ts | 4 +-- packages/kit/test/apps/basics/src/env.ts | 6 ++-- packages/kit/test/apps/options-2/src/env.ts | 4 +-- packages/kit/test/apps/options/source/env.ts | 2 +- .../kit/test/prerendering/basics/src/env.ts | 4 +-- packages/kit/types/index.d.ts | 8 ++--- 14 files changed, 46 insertions(+), 46 deletions(-) diff --git a/documentation/docs/20-core-concepts/70-environment-variables.md b/documentation/docs/20-core-concepts/70-environment-variables.md index 11fb093d03d3..26e55566d46c 100644 --- a/documentation/docs/20-core-concepts/70-environment-variables.md +++ b/documentation/docs/20-core-concepts/70-environment-variables.md @@ -144,9 +144,9 @@ You can use validators to make values optional, or transform them (such as turni The `availability` property controls when a variable's value is available, and therefore when it is validated. It has four possible values: - `'dynamic'` (default) — the value is read from the environment when the app starts, and is also validated at build time. Use this when the value is the same at build time and run time. -- `'static'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. -- `'runtime'` — the value is only available when the app runs. It is validated at run time only, so the build will succeed even if the value is absent. Use this for secrets and other variables that aren't set during the build. -- `'build'` — the value is only available during the build. It is validated at build time, but is not accessible from `$app/env/*` at run time. Use this for variables that only affect the build, such as feature flags that gate what gets compiled. +- `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. +- `'runtime'` — the value is only available when the app runs. It is validated at runtime only, so the build will succeed even if the value is absent. The value is `undefined` during the build, so the variable is typed as `T | undefined`. +- `'buildtime'` — the value is only available during the build. It is validated at buildtime, but is `undefined` at runtime. The variable is typed as `T | undefined`. ```ts /// file: src/env.ts @@ -156,7 +156,7 @@ import * as v from 'valibot'; export const variables = defineEnvVars({ SHOW_DEBUG_OVERLAY: { public: true, - +++availability: 'static',+++ + +++availability: 'inline',+++ // coerce to true/false schema: v.pipe( @@ -167,7 +167,7 @@ export const variables = defineEnvVars({ }); ``` -Because this variable is `static`, the `` component shown here will be excluded from the JavaScript bundle unless `SHOW_DEBUG_OVERLAY` is truthy: +Because this variable is `inline`, the `` component shown here will be excluded from the JavaScript bundle unless `SHOW_DEBUG_OVERLAY` is truthy: ```svelte + +

+ buildtime-only environment variable exists: {data.buildtime_environment_variable ?? 'undefined'} +

diff --git a/packages/kit/test/apps/options-2/test/env.test.js b/packages/kit/test/apps/options-2/test/env.test.js index a303e2f729ed..8a49906322a8 100644 --- a/packages/kit/test/apps/options-2/test/env.test.js +++ b/packages/kit/test/apps/options-2/test/env.test.js @@ -58,7 +58,8 @@ test.describe('$app/env', () => { PRIVATE_EXPLICIT_ENV: 'secret resolved at runtime', PRIVATE_STATIC_EXPLICIT_ENV: 'secret resolved at build time', PRIVATE_VALIDATED_DEFAULT_ENV: 'foo', - RUNTIME_ONLY: 'secret' + RUNTIME_ONLY: 'secret', + BUILDTIME_ONLY: undefined }) ); @@ -105,4 +106,13 @@ test.describe('$app/env', () => { expect(output).toContain('RUNTIME_ONLY'); } }); + + test('buildtime-only variable is undefined at runtime', async ({ page, javaScriptEnabled }) => { + test.skip(javaScriptEnabled); + + await page.goto('/basepath/env/buildtime-only'); + await expect(page.locator('p')).toHaveText( + 'buildtime-only environment variable exists: undefined' + ); + }); }); diff --git a/packages/kit/test/env/.env b/packages/kit/test/env/.env index e1e9badcab07..914b497f8c4a 100644 --- a/packages/kit/test/env/.env +++ b/packages/kit/test/env/.env @@ -3,3 +3,4 @@ MESSAGE="hello" PRIVATE_EXPLICIT_ENV="secret resolved at runtime" PRIVATE_STATIC_EXPLICIT_ENV="secret resolved at build time" +BUILDTIME_ONLY="built at build time" From 82a7c7c6891136804d078b78e1f71b04a8674825 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 8 Jul 2026 01:01:36 +0200 Subject: [PATCH 4/9] fix --- packages/kit/src/core/env.js | 46 +++++++++++++------ .../routes/env/buildtime-only/+page.server.js | 2 + .../routes/env/buildtime-only/+page.svelte | 2 +- .../kit/test/apps/options-2/test/env.test.js | 20 ++++++-- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index b3ff458b5169..58e6e42c0e05 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -198,24 +198,33 @@ export function create_sveltekit_env_private(variables, env) { /** @type {string[]} */ const exports = []; + let has_buildtime = false; + for (const [name, config] of Object.entries(variables)) { if (config.public) continue; const availability = config.availability ?? 'dynamic'; - const value = - availability === 'inline' - ? devalue.uneval(validate(variables, env[name], name, issues)) - : availability === 'buildtime' - ? 'undefined' - : `env.${name}`; + /** @type {string} */ + let value; + + if (availability === 'inline') { + value = devalue.uneval(validate(variables, env[name], name, issues)); + } else if (availability === 'buildtime') { + has_buildtime = true; + value = `building ? ${devalue.uneval(validate(variables, env[name], name, issues))} : undefined`; + } else { + value = `env.${name}`; + } exports.push(`export const ${name} = ${value};\n`); } handle_issues(issues); - return `import { dynamic_private_env as env } from '__sveltekit/env';\n\n${exports.join('')}`; + const import_building = has_buildtime ? `import { building } from '$app/env/internal';\n` : ''; + + return `${import_building}import { dynamic_private_env as env } from '__sveltekit/env';\n\n${exports.join('')}`; } /** @@ -235,24 +244,33 @@ export function create_sveltekit_env_public(variables, env, prelude) { /** @type {string[]} */ const exports = []; + let has_buildtime = false; + for (const [name, config] of Object.entries(variables)) { if (!config.public) continue; const availability = config.availability ?? 'dynamic'; - const value = - availability === 'inline' - ? devalue.uneval(validate(variables, env[name], name, issues)) - : availability === 'buildtime' - ? 'undefined' - : `env.${name}`; + /** @type {string} */ + let value; + + if (availability === 'inline') { + value = devalue.uneval(validate(variables, env[name], name, issues)); + } else if (availability === 'buildtime') { + has_buildtime = true; + value = `building ? ${devalue.uneval(validate(variables, env[name], name, issues))} : undefined`; + } else { + value = `env.${name}`; + } exports.push(`export const ${name} = ${value};\n`); } handle_issues(issues); - return `${prelude}\n\n${exports.join('')}`; + const import_building = has_buildtime ? `import { building } from '$app/env/internal';\n` : ''; + + return `${import_building}${prelude}\n\n${exports.join('')}`; } /** diff --git a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js b/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js index 6283bfb13ddc..a0e0c24eb11a 100644 --- a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js +++ b/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js @@ -1,5 +1,7 @@ import { BUILDTIME_ONLY } from '$app/env/private'; +export const prerender = true; + export function load() { return { buildtime_environment_variable: BUILDTIME_ONLY diff --git a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte b/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte index 74fb284a8901..987607b57c5c 100644 --- a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte +++ b/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte @@ -2,6 +2,6 @@ export let data; -

+

buildtime-only environment variable exists: {data.buildtime_environment_variable ?? 'undefined'}

diff --git a/packages/kit/test/apps/options-2/test/env.test.js b/packages/kit/test/apps/options-2/test/env.test.js index 8a49906322a8..597335ea8d76 100644 --- a/packages/kit/test/apps/options-2/test/env.test.js +++ b/packages/kit/test/apps/options-2/test/env.test.js @@ -107,12 +107,22 @@ test.describe('$app/env', () => { } }); - test('buildtime-only variable is undefined at runtime', async ({ page, javaScriptEnabled }) => { + test('buildtime-only variable has its value during build but is undefined at runtime', async ({ + page, + javaScriptEnabled + }) => { test.skip(javaScriptEnabled); - await page.goto('/basepath/env/buildtime-only'); - await expect(page.locator('p')).toHaveText( - 'buildtime-only environment variable exists: undefined' - ); + if (process.env.DEV) { + // in dev, building is false so the value is undefined + await page.goto('/basepath/env/buildtime-only'); + await expect(page.locator('[data-testid="buildtime-only"]')).toHaveText( + 'buildtime-only environment variable exists: undefined' + ); + } else { + // the page is prerendered during build, so the build-time value is baked into the HTML + const html = read('prerendered/pages/env/buildtime-only.html'); + expect(html).toContain('buildtime-only environment variable exists: built at build time'); + } }); }); From 01a76a022c327c2d925a74301ab189a75d6bcc5a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 10 Jul 2026 00:12:00 +0200 Subject: [PATCH 5/9] remove buildtime option because it's a giant footgun --- .../70-environment-variables.md | 5 ++-- packages/kit/src/core/env.js | 24 +++---------------- packages/kit/src/exports/public.d.ts | 9 ++++--- packages/kit/test/apps/options-2/src/env.ts | 4 ---- .../routes/env/buildtime-only/+page.server.js | 9 ------- .../routes/env/buildtime-only/+page.svelte | 7 ------ .../kit/test/apps/options-2/test/env.test.js | 22 +---------------- packages/kit/test/env/.env | 1 - packages/kit/types/index.d.ts | 9 ++++--- 9 files changed, 14 insertions(+), 76 deletions(-) delete mode 100644 packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js delete mode 100644 packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte diff --git a/documentation/docs/20-core-concepts/70-environment-variables.md b/documentation/docs/20-core-concepts/70-environment-variables.md index 26e55566d46c..8fba2be4c1cc 100644 --- a/documentation/docs/20-core-concepts/70-environment-variables.md +++ b/documentation/docs/20-core-concepts/70-environment-variables.md @@ -143,10 +143,9 @@ You can use validators to make values optional, or transform them (such as turni The `availability` property controls when a variable's value is available, and therefore when it is validated. It has four possible values: -- `'dynamic'` (default) — the value is read from the environment when the app starts, and is also validated at build time. Use this when the value is the same at build time and run time. +- `'dynamic'` (default) — the value is validated and used during runtime and buildtime. - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. -- `'runtime'` — the value is only available when the app runs. It is validated at runtime only, so the build will succeed even if the value is absent. The value is `undefined` during the build, so the variable is typed as `T | undefined`. -- `'buildtime'` — the value is only available during the build. It is validated at buildtime, but is `undefined` at runtime. The variable is typed as `T | undefined`. +- `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`. ```ts /// file: src/env.ts diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index 58e6e42c0e05..e6716a5bf437 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -120,9 +120,6 @@ export function create_sveltekit_env(variables, env, entry, is_dev) { const value = validate(variables ?? {}, env[name], name, issues); declarations.push(`explicit_public_env.${name} = ${devalue.uneval(value)};`); } - } else if (availability === 'buildtime') { - // validate at build time, but the value is `undefined` at run time - validate(variables ?? {}, env[name], name, issues); } else { const target = availability === 'runtime' ? runtime_setters : setters; @@ -198,8 +195,6 @@ export function create_sveltekit_env_private(variables, env) { /** @type {string[]} */ const exports = []; - let has_buildtime = false; - for (const [name, config] of Object.entries(variables)) { if (config.public) continue; @@ -210,9 +205,6 @@ export function create_sveltekit_env_private(variables, env) { if (availability === 'inline') { value = devalue.uneval(validate(variables, env[name], name, issues)); - } else if (availability === 'buildtime') { - has_buildtime = true; - value = `building ? ${devalue.uneval(validate(variables, env[name], name, issues))} : undefined`; } else { value = `env.${name}`; } @@ -222,9 +214,7 @@ export function create_sveltekit_env_private(variables, env) { handle_issues(issues); - const import_building = has_buildtime ? `import { building } from '$app/env/internal';\n` : ''; - - return `${import_building}import { dynamic_private_env as env } from '__sveltekit/env';\n\n${exports.join('')}`; + return `import { dynamic_private_env as env } from '__sveltekit/env';\n\n${exports.join('')}`; } /** @@ -244,8 +234,6 @@ export function create_sveltekit_env_public(variables, env, prelude) { /** @type {string[]} */ const exports = []; - let has_buildtime = false; - for (const [name, config] of Object.entries(variables)) { if (!config.public) continue; @@ -256,9 +244,6 @@ export function create_sveltekit_env_public(variables, env, prelude) { if (availability === 'inline') { value = devalue.uneval(validate(variables, env[name], name, issues)); - } else if (availability === 'buildtime') { - has_buildtime = true; - value = `building ? ${devalue.uneval(validate(variables, env[name], name, issues))} : undefined`; } else { value = `env.${name}`; } @@ -268,9 +253,7 @@ export function create_sveltekit_env_public(variables, env, prelude) { handle_issues(issues); - const import_building = has_buildtime ? `import { building } from '$app/env/internal';\n` : ''; - - return `${import_building}${prelude}\n\n${exports.join('')}`; + return `${prelude}\n\n${exports.join('')}`; } /** @@ -318,7 +301,6 @@ export function create_sveltekit_env_service_worker_dev(variables, env, global) for (const [name, config] of Object.entries(variables ?? {})) { if (!config.public) continue; - if ((config.availability ?? 'dynamic') === 'buildtime') continue; const value = validate(variables ?? {}, env[name], name, issues); properties.push(`${name}: ${devalue.uneval(value)}`); @@ -356,7 +338,7 @@ export function create_explicit_env_types(variables, relative, type) { .map(([name, config]) => { const comment = config.description ? `${create_jsdoc(config.description)}\n` : ''; const availability = config.availability ?? 'dynamic'; - const maybe_undefined = availability === 'buildtime' || availability === 'runtime'; + const maybe_undefined = availability === 'runtime'; const type = config.schema ? `import('@sveltejs/kit/internal/types').StandardSchemaV1.InferOutput${maybe_undefined ? ' | undefined' : ''}` : maybe_undefined diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index bd88942b68ba..1f1167f58e1c 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -2448,13 +2448,12 @@ export interface EnvVarConfig { public?: boolean; /** * When the variable's value is available, and therefore when it is validated. - * - `'inline'` — the value is inlined into the bundle at build time, enabling optimisations like dead-code elimination. It is validated at build time. - * - `'dynamic'` — the value is read from the environment when the app starts. It is validated at both build time and run time. - * - `'buildtime'` — the value is only available during the build. It is validated at build time, but is `undefined` at runtime. - * - `'runtime'` — the value is only available when the app runs. It is validated at run time only, allowing the build to succeed even if the value is absent. During the build, the value is `undefined`. + * - `'dynamic'` — the value is validated and used during runtime and buildtime. + * - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. + * - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`. * @default 'dynamic' */ - availability?: 'inline' | 'dynamic' | 'buildtime' | 'runtime'; + availability?: 'inline' | 'dynamic' | 'runtime'; /** * A [Standard Schema](https://standardschema.dev/) validator that is applied to the value when the app starts. * The validator can output any value — not necessarily a string — but public, non-static values must be diff --git a/packages/kit/test/apps/options-2/src/env.ts b/packages/kit/test/apps/options-2/src/env.ts index 530bb5263abf..916e4c0fe0ff 100644 --- a/packages/kit/test/apps/options-2/src/env.ts +++ b/packages/kit/test/apps/options-2/src/env.ts @@ -17,9 +17,5 @@ export const variables = { RUNTIME_ONLY: { availability: 'runtime', schema: v.string() - }, - BUILDTIME_ONLY: { - availability: 'buildtime', - schema: v.string() } }; diff --git a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js b/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js deleted file mode 100644 index a0e0c24eb11a..000000000000 --- a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.server.js +++ /dev/null @@ -1,9 +0,0 @@ -import { BUILDTIME_ONLY } from '$app/env/private'; - -export const prerender = true; - -export function load() { - return { - buildtime_environment_variable: BUILDTIME_ONLY - }; -} diff --git a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte b/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte deleted file mode 100644 index 987607b57c5c..000000000000 --- a/packages/kit/test/apps/options-2/src/routes/env/buildtime-only/+page.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -

- buildtime-only environment variable exists: {data.buildtime_environment_variable ?? 'undefined'} -

diff --git a/packages/kit/test/apps/options-2/test/env.test.js b/packages/kit/test/apps/options-2/test/env.test.js index 597335ea8d76..a303e2f729ed 100644 --- a/packages/kit/test/apps/options-2/test/env.test.js +++ b/packages/kit/test/apps/options-2/test/env.test.js @@ -58,8 +58,7 @@ test.describe('$app/env', () => { PRIVATE_EXPLICIT_ENV: 'secret resolved at runtime', PRIVATE_STATIC_EXPLICIT_ENV: 'secret resolved at build time', PRIVATE_VALIDATED_DEFAULT_ENV: 'foo', - RUNTIME_ONLY: 'secret', - BUILDTIME_ONLY: undefined + RUNTIME_ONLY: 'secret' }) ); @@ -106,23 +105,4 @@ test.describe('$app/env', () => { expect(output).toContain('RUNTIME_ONLY'); } }); - - test('buildtime-only variable has its value during build but is undefined at runtime', async ({ - page, - javaScriptEnabled - }) => { - test.skip(javaScriptEnabled); - - if (process.env.DEV) { - // in dev, building is false so the value is undefined - await page.goto('/basepath/env/buildtime-only'); - await expect(page.locator('[data-testid="buildtime-only"]')).toHaveText( - 'buildtime-only environment variable exists: undefined' - ); - } else { - // the page is prerendered during build, so the build-time value is baked into the HTML - const html = read('prerendered/pages/env/buildtime-only.html'); - expect(html).toContain('buildtime-only environment variable exists: built at build time'); - } - }); }); diff --git a/packages/kit/test/env/.env b/packages/kit/test/env/.env index 914b497f8c4a..e1e9badcab07 100644 --- a/packages/kit/test/env/.env +++ b/packages/kit/test/env/.env @@ -3,4 +3,3 @@ MESSAGE="hello" PRIVATE_EXPLICIT_ENV="secret resolved at runtime" PRIVATE_STATIC_EXPLICIT_ENV="secret resolved at build time" -BUILDTIME_ONLY="built at build time" diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 988896e5f38f..1483d742637f 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2419,13 +2419,12 @@ declare module '@sveltejs/kit' { public?: boolean; /** * When the variable's value is available, and therefore when it is validated. - * - `'inline'` — the value is inlined into the bundle at build time, enabling optimisations like dead-code elimination. It is validated at build time. - * - `'dynamic'` — the value is read from the environment when the app starts. It is validated at both build time and run time. - * - `'buildtime'` — the value is only available during the build. It is validated at build time, but is `undefined` at run time. - * - `'runtime'` — the value is only available when the app runs. It is validated at run time only, allowing the build to succeed even if the value is absent. During the build, the value is `undefined`. + * - `'dynamic'` — the value is validated and used during runtime and buildtime. + * - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. + * - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`. * @default 'dynamic' */ - availability?: 'inline' | 'dynamic' | 'buildtime' | 'runtime'; + availability?: 'inline' | 'dynamic' | 'runtime'; /** * A [Standard Schema](https://standardschema.dev/) validator that is applied to the value when the app starts. * The validator can output any value — not necessarily a string — but public, non-static values must be From b5414bccdacf5be6519ded326067f8d87e4f7b3c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 10 Jul 2026 01:05:24 +0200 Subject: [PATCH 6/9] lint --- packages/kit/src/core/env.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index e6716a5bf437..ae3af7a5f62b 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -105,8 +105,11 @@ export function create_sveltekit_env(variables, env, entry, is_dev) { ] : [`const variables = {};`, `const handle_issues = () => {};`]; + /** @type {string[]} */ const declarations = []; + /** @type {string[]} */ const setters = []; + /** @type {string[]} */ const runtime_setters = []; /** @type {Record} */ From 214b14e8cfe31ed6b1d0250cfcf5c8c3a64bba4a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 10 Jul 2026 14:12:06 +0200 Subject: [PATCH 7/9] tweak --- packages/kit/src/core/adapt/builder.js | 4 +-- packages/kit/src/core/env.js | 39 ++++++++------------------ packages/kit/src/exports/vite/index.js | 8 ++---- 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 9987ba843205..6664a2db4aed 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -168,8 +168,8 @@ export function create_builder({ for (const [name, config] of Object.entries(variables)) { const availability = config.availability ?? 'dynamic'; - // only `dynamic` public vars are included in the prerendered env.js — - // `inline` is inlined, `buildtime` is undefined at runtime, and `runtime` isn't available at build time + // only `dynamic` public vars are included in the prerendered env.js. + // `inline` is inlined, and `runtime` isn't available at build time if (!config.public || availability !== 'dynamic') continue; values[name] = validate(variables, env[name], name, issues); } diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index ae3af7a5f62b..f8ff4a21c9eb 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -201,16 +201,10 @@ export function create_sveltekit_env_private(variables, env) { for (const [name, config] of Object.entries(variables)) { if (config.public) continue; - const availability = config.availability ?? 'dynamic'; - - /** @type {string} */ - let value; - - if (availability === 'inline') { - value = devalue.uneval(validate(variables, env[name], name, issues)); - } else { - value = `env.${name}`; - } + const value = + config.availability === 'inline' + ? devalue.uneval(validate(variables, env[name], name, issues)) + : `env.${name}`; exports.push(`export const ${name} = ${value};\n`); } @@ -240,16 +234,10 @@ export function create_sveltekit_env_public(variables, env, prelude) { for (const [name, config] of Object.entries(variables)) { if (!config.public) continue; - const availability = config.availability ?? 'dynamic'; - - /** @type {string} */ - let value; - - if (availability === 'inline') { - value = devalue.uneval(validate(variables, env[name], name, issues)); - } else { - value = `env.${name}`; - } + const value = + config.availability === 'inline' + ? devalue.uneval(validate(variables, env[name], name, issues)) + : `env.${name}`; exports.push(`export const ${name} = ${value};\n`); } @@ -270,11 +258,9 @@ export function create_sveltekit_env_public(variables, env, prelude) { * @param {string} app_dir */ export function create_sveltekit_env_service_worker(variables, env, global, base, app_dir) { - const has_dynamic_public_env = Object.values(variables ?? {}).some((config) => { - if (!config.public) return false; - const availability = config.availability ?? 'dynamic'; - return availability === 'dynamic' || availability === 'runtime'; - }); + const has_dynamic_public_env = Object.values(variables ?? {}).some( + (config) => config.public && config.availability !== 'inline' + ); if (!has_dynamic_public_env) { return create_sveltekit_env_service_worker_dev(variables, env, global); @@ -340,8 +326,7 @@ export function create_explicit_env_types(variables, relative, type) { .filter(([_, config]) => !!config.public === (type === 'public')) .map(([name, config]) => { const comment = config.description ? `${create_jsdoc(config.description)}\n` : ''; - const availability = config.availability ?? 'dynamic'; - const maybe_undefined = availability === 'runtime'; + const maybe_undefined = config.availability === 'runtime'; const type = config.schema ? `import('@sveltejs/kit/internal/types').StandardSchemaV1.InferOutput${maybe_undefined ? ' | undefined' : ''}` : maybe_undefined diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 67f04a5c3a47..edd422c0358a 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -1648,15 +1648,11 @@ function kit({ svelte_config }) { find_deps(vite_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root); const has_explicit_dynamic_public_env = Object.values(explicit_env_config ?? {}).some( - (variable) => { - if (!variable.public) return false; - const availability = variable.availability ?? 'dynamic'; - return availability === 'dynamic' || availability === 'runtime'; - } + (variable) => variable.public && variable.availability !== 'inline' ); // the app only depends on runtime public env if it imports `$app/env/public` - // *and* at least one public env var is read at run time (i.e. not inline or buildtime) + // *and* at least one public env var is actually dynamic (not inlined at build time) const uses_env_dynamic_public = has_explicit_dynamic_public_env && client_chunks.some( From 145562443901ceef7cb0dee65f10a6dc72dda472 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:39:21 +0200 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Tee Ming --- .../docs/20-core-concepts/70-environment-variables.md | 4 ++-- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/types/index.d.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/docs/20-core-concepts/70-environment-variables.md b/documentation/docs/20-core-concepts/70-environment-variables.md index 8fba2be4c1cc..8e73d34e7606 100644 --- a/documentation/docs/20-core-concepts/70-environment-variables.md +++ b/documentation/docs/20-core-concepts/70-environment-variables.md @@ -141,9 +141,9 @@ You can use validators to make values optional, or transform them (such as turni ### Availability -The `availability` property controls when a variable's value is available, and therefore when it is validated. It has four possible values: +The `availability` property controls when a variable's value is available, and therefore when it is validated. It has three possible values: -- `'dynamic'` (default) — the value is validated and used during runtime and buildtime. +- `'dynamic'` (default) — the value is validated and used during runtime and build time. - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`. diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 76cd1b165877..c8f78eb1dbe9 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -2453,7 +2453,7 @@ export interface EnvVarConfig { public?: boolean; /** * When the variable's value is available, and therefore when it is validated. - * - `'dynamic'` — the value is validated and used during runtime and buildtime. + * - `'dynamic'` — the value is validated and used during runtime and build time. * - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. * - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`. * @default 'dynamic' diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index eca7cbe5326b..b356f02e3536 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -2423,7 +2423,7 @@ declare module '@sveltejs/kit' { public?: boolean; /** * When the variable's value is available, and therefore when it is validated. - * - `'dynamic'` — the value is validated and used during runtime and buildtime. + * - `'dynamic'` — the value is validated and used during runtime and build time. * - `'inline'` — the value is inlined into your application code at build time, enabling optimisations like dead-code elimination. It is validated at build time. * - `'runtime'` — the value is validated and used during runtime only. The value is `undefined` during the build, so the variable is typed as `T | undefined`. * @default 'dynamic' From c6022386a403cfff1ed80fcf6b4915a0c8293d41 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 10 Jul 2026 22:41:09 +0200 Subject: [PATCH 9/9] help with migration --- packages/kit/src/core/env.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index b918bf2ce054..04830db4dd3f 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -116,6 +116,10 @@ export function create_sveltekit_env(variables, env, entry, is_dev) { const issues = {}; for (const [name, config] of Object.entries(variables ?? {})) { + if (/** @type {any} */ (config)?.static) { + throw new Error('`static: true` has been removed. Use `availability: "inline"` instead.'); + } + const availability = config?.availability ?? 'dynamic'; if (availability === 'inline') {