# .env.example template

> A .env.example file lists every environment variable your app needs, with safe defaults and empty secrets, so a new clone knows what to fill in. Copy the template below to the root of your repository, commit it, and keep the real values in an uncommitted .env file.

Updated November 8, 2026. Canonical: https://sniptide.com/templates/env-example
Open it in the Sniptide editor: https://app.sniptide.com/new?template=env-example
Files as JSON: https://sniptide.com/templates/env-example.json

## Files

### .env.example

```dotenv
# Copy this file to .env and fill in the values: cp .env.example .env
# Commit .env.example. Never commit .env.

# --- App ---
# development, test or production
NODE_ENV=development
# The public URL of the app, without a trailing slash
APP_URL=http://localhost:3000
PORT=3000

# --- Database ---
# Matches the Postgres service in docker-compose.yml
DATABASE_URL=postgres://app:app@localhost:5432/app

# --- Auth ---
# A random string of at least 32 characters: openssl rand -base64 32
AUTH_SECRET=
# OAuth app from https://github.com/settings/developers
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# --- Email ---
# Leave empty in development to print emails to the console
SMTP_URL=
EMAIL_FROM="Acme <hello@example.com>"

# --- Optional ---
# Error reporting. Leave empty to turn it off.
SENTRY_DSN=
LOG_LEVEL=info
```

## How the file works [#how-the-file-works]

A new teammate or an agent runs `cp .env.example .env`, fills in the blanks and starts the app. The example file is committed and the real `.env` is not. Add this line to `.gitignore` before anything else:

```bash title=".gitignore"
.env
.env*.local
```

## What each part does [#what-each-part-does]

| Part | Rule | Why |
| --- | --- | --- |
| Header comment | Says how to copy the file and what not to commit. | It is the first thing someone opens on a fresh clone. |
| Groups | One `# --- Name ---` comment per service. | Long files stay scannable, and a missing group stands out. |
| Defaults | Real values that work on a laptop, such as `localhost` URLs. | The app starts with only the secrets filled in. |
| Secrets | Left empty, with a comment saying where to get one. | An empty value fails loudly. A fake one fails in confusing ways. |
| Optional | Grouped last, with what happens when they are empty. | People know which blanks they can skip. |

Quote a value that contains spaces, as `EMAIL_FROM` does. Comments go on their own line, because some parsers read a `#` after a value as part of the value.

## Keep it in sync with the code [#keep-it-in-sync-with-the-code]

The file goes stale the day someone reads a new variable without adding it here. Validate the environment when the app starts, so a missing variable stops the app with a clear message:

```ts title="lib/env.ts"
import { z } from "zod"

const schema = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]),
  APP_URL: z.url(),
  DATABASE_URL: z.string().min(1),
  AUTH_SECRET: z.string().min(32),
  SENTRY_DSN: z.string().optional(),
})

export const env = schema.parse(process.env)
```

## Variant: a Next.js app [#variant-a-nextjs-app]

Next.js loads `.env.local` and sends variables that start with `NEXT_PUBLIC_` to the browser. Mark those clearly, because anything with the prefix is public.

```bash title=".env.example"
# Server only
DATABASE_URL=postgres://app:app@localhost:5432/app
AUTH_SECRET=

# Sent to the browser. Never put a secret here.
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_POSTHOG_KEY=
```

Copy it with `cp .env.example .env.local` in a Next.js project.

## Share the real values [#share-the-real-values]

The example file shows what is needed, and the values still have to reach each person. Sending a `.env` over chat leaves a copy in every thread. Sniptide stores the variables as an encrypted env group. You upload yours with `sniptide env push my-app`, and a new teammate writes their `.env` with `sniptide env pull my-app` instead of asking for keys. The [secrets guide](https://docs.sniptide.com/core-concepts/secrets) covers the setup.
