While Astro has astro:env to help with environment variables, we think Varlock has more to offer:
- Your
.env.schemais not tied to JavaScript, and is a better place to store this schema info versus yourastro.config.*file - Facilitates loading and composing multiple
.envfiles - You can use validated env vars right away within your
astro.config.*file - Facilitates setting values and handling multiple environments, not just setting defaults
- More data types and options available
- Leak detection, log redaction, and more security guardrails
To integrate varlock into an Astro application, you must use our @varlock/astro-integration package, which is an Astro integration.
-
Install varlock and the Astro integration package
Terminal window npm install @varlock/astro-integration varlockTerminal window pnpm add @varlock/astro-integration varlockTerminal window bun add @varlock/astro-integration varlockTerminal window yarn add @varlock/astro-integration varlockTerminal window vlt install @varlock/astro-integration varlock -
Run
varlock initto set up your.env.schemafileThis will guide you through setting up your
.env.schemafile, based on your existing.envfile(s). Make sure to review it carefully.Terminal window npm exec -- varlock initTerminal window pnpm exec -- varlock initTerminal window bunx varlock initTerminal window vlx -- varlock initTerminal window yarn exec -- varlock init -
Enable the Astro integration
You must add our
varlockAstroIntegrationto yourastro.config.*file:astro.config.ts import { defineConfig } from 'astro/config';import varlockAstroIntegration from '@varlock/astro-integration';export default defineConfig({integrations: [varlockAstroIntegration(), otherIntegration()],});
Accessing environment variables
Section titled “Accessing environment variables”You can continue to use import.meta.env.SOMEVAR as usual, but we recommend using varlock’s imported ENV object for better type-safety and improved developer experience:
import { ENV } from 'varlock/env';
console.log(import.meta.env.SOMEVAR); // 🆗 still worksconsole.log(ENV.SOMEVAR); // ✨ recommendedWhy use ENV instead of import.meta.env?
Section titled “Why use ENV instead of import.meta.env?”- Non-string values (e.g., number, boolean) are properly typed and coerced
- All non-sensitive items are replaced at build time (not just
VITE_prefixed ones) - Better error messages for invalid or unavailable keys
- Enables future DX improvements and tighter control over what is bundled
Within astro.config.*
Section titled “Within astro.config.*”It’s often useful to be able to access env vars in your Astro config. Without varlock, it’s a bit awkward, but varlock makes it easy. In fact it’s already available: import varlock’s ENV object and reference env vars via ENV.SOME_ITEM like you do everywhere else.
import { defineConfig } from 'astro/config';import varlockAstroIntegration from '@varlock/astro-integration';import { ENV } from 'varlock/env';
doSomethingWithEnvVar(ENV.FOO);
export default defineConfig({ /* ... */ });Within other scripts
Section titled “Within other scripts”Even in a static front-end project, you may have other scripts in your project that rely on sensitive config.
You can use varlock run to inject resolved config into other scripts as regular env vars.
npm exec -- varlock run -- node ./script.jspnpm exec -- varlock run -- node ./script.jsbunx varlock run -- node ./script.jsvlx -- varlock run -- node ./script.jsyarn exec -- varlock run -- node ./script.jsType-safety and IntelliSense
Section titled “Type-safety and IntelliSense”To enable type-safety and IntelliSense for your env vars, enable the @generateTsTypes root decorator in your .env.schema. Note that if your schema was created using varlock init, it will include this by default.
# @generateTsTypes(path='env.d.ts')# ---# your config items...Managing multiple environments
Section titled “Managing multiple environments”Varlock can load multiple environment-specific .env files (e.g., .env.development, .env.preview, .env.production) by using the @currentEnv root decorator. This is different than Astro/Vite’s default behaviour, which relies on it’s own MODE flag.
Usually this env var will be defaulted to something like development in your .env.schema file, and you can override it by overriding the value when running commands - for example APP_ENV=production vite build. For a JavaScript based project, this will often be done in your package.json scripts.
{ "scripts": { "dev": "astro dev", "build": "APP_ENV=production astro build", "preview": "APP_ENV=production vite preview", }}In some cases, you could also set the current environment value based on other vars already injected by your CI platform, like the current branch name. See the environments guide for more information.
Managing sensitive config values
Section titled “Managing sensitive config values”Astro uses the PUBLIC_ prefix to determine which env vars are public (bundled for the browser). Varlock decouples the concept of being sensitive from key names, and instead you control this with the @defaultSensitive root decorator and the @sensitive item decorator. See the secrets guide for more information.
Set a default and explicitly mark items:
# @defaultSensitive=false# ---NON_SECRET_FOO= # sensitive by default# @sensitiveSECRET_FOO=Or if you’d like to continue using Astro’s prefix behavior:
# @defaultSensitive=inferFromPrefix('PUBLIC_')# ---FOO= # sensitivePUBLIC_FOO= # non-sensitive, due to prefixLeak Detection
Section titled “Leak Detection”This integration will automatically inject a new middleware that scans outgoing http responses for any sensitive values.
Deploying to Cloudflare Workers
Section titled “Deploying to Cloudflare Workers”If you deploy your SSR Astro app to Cloudflare Workers via @astrojs/cloudflare, install @varlock/cloudflare-integration alongside the Astro integration. The Astro integration auto-detects the Cloudflare adapter and wires up env injection into the worker. There’s no extra Vite or adapter config to write, and you keep using astro dev as normal.
Static and prerendered pages work too: they are rendered at build time with your resolved env, so a fully static site (output: 'static') needs no vars or secrets uploaded to Cloudflare at all.
-
Install the Cloudflare integration
Terminal window npm install @varlock/cloudflare-integrationTerminal window pnpm add @varlock/cloudflare-integrationTerminal window bun add @varlock/cloudflare-integrationTerminal window yarn add @varlock/cloudflare-integrationTerminal window vlt install @varlock/cloudflare-integration -
Use the Cloudflare adapter as usual
No varlock-specific Cloudflare setup is needed. Use the standard adapter alongside
varlockAstroIntegration():astro.config.ts import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';import varlockAstroIntegration from '@varlock/astro-integration';export default defineConfig({output: 'server',adapter: cloudflare(),integrations: [varlockAstroIntegration()],}); -
Deploy with
varlock-wranglerUse
varlock-wranglerso your resolved env is uploaded as Cloudflare vars and secrets at deploy time:package.json {"scripts": {"dev": "astro dev","build": "astro build","deploy": "astro build && varlock-wrangler deploy"}}
See the Cloudflare Workers integration docs for full details on how dev injection, deployment, CI/CD, and varlock-wrangler work.
Dynamic+public config
Section titled “Dynamic+public config”Use @dynamic to keep selected public values runtime-resolved; server-rendered code keeps reading ENV.KEY with no extra setup. See the Static vs Dynamic Config guide for the full model.
PUBLIC_STATIC_FLAG=enabled # @publicPUBLIC_RUNTIME_FLAG=enabled # @public @dynamicClient-side usage
Section titled “Client-side usage”When the schema contains any public+dynamic keys, this integration automatically injects a route at /__varlock/public-env (SSR builds only) that serves them to the browser. If browser code needs one of these values, load it with loadPublicDynamicEnv() before reading ENV.KEY; this is rarely needed, but when it is, see Loading dynamic vars in the client.
Route injection options
Section titled “Route injection options”You can control endpoint injection:
import { defineConfig } from 'astro/config';import varlockAstroIntegration from '@varlock/astro-integration';
export default defineConfig({ integrations: [ varlockAstroIntegration({ publicDynamicEndpoint: true, // auto by default // or: { path: '/my-public-env' } // or: false (disable auto endpoint) }), ],});