Skip to content

SvelteKit

SvelteKit is built on Vite, so there’s no dedicated SvelteKit package — which integration you use depends on where you deploy.

  • Node / Vercel / Netlify / self-hosted / most adapters — use the Vite integration. See setup below.
  • Cloudflare Workers (via @sveltejs/adapter-cloudflare) — use varlockSvelteKitCloudflarePlugin from @varlock/cloudflare-integration/sveltekit. See setup below.

Check out the SvelteKit example project for a working reference.


For any SvelteKit deployment target other than Cloudflare Workers, use varlockVitePlugin exactly as you would in any Vite project.

  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 varlockSvelteKitCloudflarePlugin from @varlock/cloudflare-integration/sveltekit. This is a separate entry point from the standard varlockCloudflareVitePlugin because @cloudflare/vite-plugin doesn’t currently support SvelteKit (see cloudflare/workers-sdk#8922). The SvelteKit plugin skips that dependency entirely and injects the runtime env-loader into SvelteKit’s SSR entry instead, so the resolved env is available inside the worker.

  1. Install packages

    Terminal window
    npm install @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

    vite.config.ts
    import { sveltekit } from '@sveltejs/kit/vite';
    import { defineConfig } from 'vite';
    import { varlockSvelteKitCloudflarePlugin } from '@varlock/cloudflare-integration/sveltekit';
    export default defineConfig({
    plugins: [
    varlockSvelteKitCloudflarePlugin(),
    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 — 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)