Skip to content

SvelteKit

SvelteKit is built on Vite, so there’s no dedicated SvelteKit package. You use the Vite integration the same way regardless of where you deploy.

Add varlockVitePlugin() to your Vite config (setup below) and it works across deploy targets. If you deploy to Cloudflare Workers (via @sveltejs/adapter-cloudflare), the plugin detects the adapter and automatically wires up the runtime env-loader. You just need @varlock/cloudflare-integration installed as well (it ships varlock-wrangler). See Cloudflare Workers setup.

Check out the SvelteKit example project for a working reference.


Use varlockVitePlugin exactly as you would in any Vite project. This is the setup for every deploy target. See Cloudflare Workers below for the one extra package CF deployments need.

  1. Install packages

    Terminal window
    npm install @varlock/vite-integration varlock
  2. Run varlock init to set up your .env.schema

    Terminal window
    npm exec -- varlock init
  3. Add the plugin to your Vite config. It should come before sveltekit():

    vite.config.ts
    import { sveltekit } from '@sveltejs/kit/vite';
    import { defineConfig } from 'vite';
    import { varlockVitePlugin } from '@varlock/vite-integration';
    export default defineConfig({
    plugins: [
    varlockVitePlugin(),
    sveltekit(),
    ],
    });

For SvelteKit projects deploying to Cloudflare Workers via @sveltejs/adapter-cloudflare, use the same varlockVitePlugin() from the setup above. When it detects the Cloudflare adapter, it automatically injects the runtime env-loader into SvelteKit’s SSR entry so the resolved env is available inside the worker. You just need to additionally install @varlock/cloudflare-integration, which provides varlock-wrangler and the Cloudflare-specific loader the plugin pulls in. (The standard varlockCloudflareVitePlugin isn’t used here because @cloudflare/vite-plugin doesn’t currently support SvelteKit. See cloudflare/workers-sdk#8922.)

The adapter is detected whether you configure it in svelte.config.js or inline in your Vite config (SvelteKit ≥ 2.62), so no extra setup is needed either way.

  1. Install packages

    Terminal window
    npm install @varlock/vite-integration @varlock/cloudflare-integration varlock
  2. Run varlock init to set up your .env.schema

    Terminal window
    npm exec -- varlock init
  3. Add the plugin to your Vite config. This is the same as the base setup; it auto-detects the Cloudflare adapter:

    vite.config.ts
    import { sveltekit } from '@sveltejs/kit/vite';
    import { defineConfig } from 'vite';
    import { varlockVitePlugin } from '@varlock/vite-integration';
    export default defineConfig({
    plugins: [
    varlockVitePlugin(),
    sveltekit(),
    ],
    });
  4. Deploy with varlock-wrangler

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

    package.json
    {
    "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "deploy": "npm run build && varlock-wrangler deploy"
    }
    }

    If you deploy via Cloudflare Workers Builds instead of a local/CI script, override the deploy command in your Cloudflare dashboard under Settings → Build → Deploy command. See Workers Builds below.

  • In dev: vite dev runs SvelteKit’s dev server; varlock resolves env via its normal flow and makes it available on ENV.*.
  • In production: The SvelteKit SSR bundle has a cloudflare:workers runtime loader injected at the top of its server entry. At worker boot, it reads the __VARLOCK_ENV binding and hydrates varlock’s runtime. The loader is guarded by a navigator.userAgent === 'Cloudflare-Workers' check so SvelteKit’s Node-side postbuild steps (prerender, fallback) don’t try to resolve cloudflare:workers.
  • varlock-wrangler deploy uploads non-sensitive values as Cloudflare vars and sensitive values as secrets.

If you deploy through Cloudflare Workers Builds, two pieces of configuration must be set in the dashboard, and neither can be committed to the repo:

  • Override the Deploy command. Under Settings → Build → Deploy command, replace the default npx wrangler deploy with npx varlock-wrangler deploy. Without this, Cloudflare runs stock wrangler deploy, which skips varlock resolution and leaves your worker without its resolved vars/secrets.
  • Set any secret-zero vars under Build variables. Any env vars varlock itself needs during load (e.g. a 1Password service account token, a GCP key) must be set under Settings → Build → Variables and Secrets so they’re available at build time. varlock-wrangler deploy then resolves your full env graph and uploads the result to the worker runtime as regular vars/secrets.

SvelteKit ships four built-in env modules ($env/static/private, $env/static/public, $env/dynamic/private, $env/dynamic/public) which split env vars along two axes: static vs dynamic (inlined at build vs looked up at runtime) and private vs public (server-only vs bundled for the browser, gated by the PUBLIC_ prefix).

The key conceptual shift is that SvelteKit’s system is access-driven: the same underlying variable behaves differently depending on which module you import it from, and public/private is inferred from the variable’s name. Varlock is schema-driven: each item is declared once in .env.schema with decorators that determine its behavior (sensitivity, type, validation, whether it’s inlined or resolved at runtime), and every access site uses the same ENV object. You describe the variable once, and that description is authoritative everywhere it’s used.

Varlock replaces all four $env/* modules with a single ENV object from varlock/env. The same two axes still exist, but they’re controlled by the schema rather than the import path:

SvelteKit conceptVarlock equivalent
$env/static/publicENV.FOO where @sensitive=false; non-sensitive values are inlined at build time on both client and server
$env/static/privateENV.FOO where @sensitive=true, referenced from SSR/server code; inlined into the server bundle, build errors if referenced from client code
$env/dynamic/publicNot yet supported. Non-sensitive values are currently always inlined at build time (equivalent to $env/static/public). Runtime-resolved public values are on the roadmap.
$env/dynamic/privateENV.FOO; in server contexts it’s read from the live runtime env (e.g. Cloudflare bindings via varlockCloudflareVitePlugin, or Node’s process.env elsewhere) rather than baked into the bundle
PUBLIC_ prefix requirementPer-item @sensitive decorator, or a schema-wide @defaultSensitive rule

Everywhere you currently import from $env/*, switch to varlock/env:

src/routes/+page.server.ts
import { API_KEY } from '$env/static/private';
import { PUBLIC_API_URL } from '$env/static/public';
import { ENV } from 'varlock/env';
export const load = async () => {
const res = await fetch(`${PUBLIC_API_URL}/data`, {
headers: { Authorization: `Bearer ${API_KEY}` },
const res = await fetch(`${ENV.PUBLIC_API_URL}/data`, {
headers: { Authorization: `Bearer ${ENV.API_KEY}` },
});
};

ENV works from both +page.svelte (client) and +page.server.ts/+server.ts (server); there’s no need to import from different paths depending on context. Varlock enforces the public/private boundary with both build-time checks (sensitive values are never included in the client bundle, and referencing one from client-reachable code surfaces as a build error) and runtime checks (log redaction and leak detection in server responses) so a mistake in either layer is caught rather than silently exposing a secret.

SvelteKit uses the PUBLIC_ prefix to determine which vars are safe to bundle for the browser. Varlock uses decorators, either per-item or schema-wide via @defaultSensitive.

The simplest setup is to mark items explicitly:

.env.schema
# @defaultSensitive=true
# ---
API_KEY= # sensitive by default
# @sensitive=false
PUBLIC_API_URL= # explicitly non-sensitive (bundled for browser)

If you’d rather keep SvelteKit’s PUBLIC_-prefix convention, you can infer sensitivity from the name:

.env.schema
# @defaultSensitive=inferFromPrefix('PUBLIC_')
# ---
API_KEY= # sensitive (no prefix)
PUBLIC_API_URL= # non-sensitive (has PUBLIC_ prefix)