# Quickstart (/docs/getting-started/quickstart)



A working setup has three files: the **schema** (contract), one or more **per-environment config files** (typed values), and the **wiring** (`defineEnv` + `loadConfig`).

```text title="project layout"
src/
  env/
    schema.ts          # the contract
    index.ts           # wiring
  config/
    development.ts
    production.ts
```

## 1. Define the schema [#1-define-the-schema]

The schema is the contract. Every variable your app expects is declared here, grouped into branches, and each leaf is a Standard Schema validator that runs at boot.

```ts title="src/env/schema.ts"
import { schema, type Config } from "@vlandoss/env";
import * as z from "zod";

export const Env = schema({
  log: {
    LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"])
  },
  server: {
    HOST: z.string(),
    PORT: z.coerce.number().int().positive()
  },
  db: {
    URL: z.string()
  },
});

export type EnvConfig = Config<typeof Env>;
```

## 2. Write a per-environment config file [#2-write-a-per-environment-config-file]

`EnvConfig` is type-checked against the schema — typos and wrong types fail at compile time.

```ts title="src/config/development.ts"
import type { EnvConfig } from "../env/schema.ts";

export default {
  log: { LEVEL: "debug" },
  server: { PORT: 3000, HOST: "localhost" },
  db: { URL: "postgres://localhost/dev" },
} satisfies EnvConfig;
```

## 3. Wire it up [#3-wire-it-up]

```ts title="src/env/index.ts"
import { defineEnv } from "@vlandoss/env";
import { loadConfig } from "@vlandoss/env/fs";
import { Env } from "./schema.ts";

const config = loadConfig(Env);
export const env = defineEnv({ schema: Env, config });
```

`loadConfig(Env)` **synchronously** auto-discovers `[src/]config/<envName>.{ts,mts,cts,js,mjs,cjs,json}` under `process.cwd()` (no `await`). `defineEnv` then merges `config` with `process.env` and validates against the schema. If anything is missing or wrong, it throws naming the dot-path of the offending leaf.

## 4. Read typed values [#4-read-typed-values]

```ts title="src/app.ts"
import { env } from "./env/index.ts";

console.log(env.server.PORT);  // number
console.log(env.db.URL);       // string
console.log(env.$name);        // "development" | "production" | …
if (env.IS_PROD) { /* ... */ }
```

The shape of `env` mirrors your schema exactly: `env.server.PORT` is `number` because `z.coerce.number()` ran during validation, `env.db.URL` is `string`. Any path that isn't in the schema (`env.cache.TTL`, `env.server.PROT`) is a compile error, so refactors and typos surface in the type checker before they reach a running app.

## Where to go next [#where-to-go-next]

* **[Resolution order](/docs/concepts/resolution)** — how defaults, config files, and env vars combine.
* **[Env-var naming](/docs/concepts/env-var-naming)** — the `SERVER_PORT` convention and how to override it.
* **[SPA / browser](/docs/guides/spa-dynamic-import)** — the same pattern without a file system.
* **[SSR](/docs/guides/ssr)** — splitting public values from server-only secrets.
