Configuration and environment variable conventions for the Playwright scaffold — env file layout (env/.env.*), dotenv loading via playwright.config.ts and the ENVIRONMENT variable, config objects in config/app.ts and config/util/util.ts, and the rules for adding new env-driven values. Use when adding a new environment variable, a new config property, a new environment file, a new utility-service URL, or when a test/fixture needs to consume APP_URL / API_URL / APP_EMAIL / APP_PASSWORD / UTILIT...
Installs into .claude/skills of the current project.
Are you the author of Config?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/idavidov13-config)
---
name: config
description: Configuration and environment variable conventions for the Playwright scaffold — env file layout (env/.env.*), dotenv loading via playwright.config.ts and the ENVIRONMENT variable, config objects in config/app.ts and config/util/util.ts, and the rules for adding new env-driven values. Use when adding a new environment variable, a new config property, a new environment file, a new utility-service URL, or when a test/fixture needs to consume APP_URL / API_URL / APP_EMAIL / APP_PASSWORD / UTILITY_URL. This skill owns URLs, credentials, and env-driven settings — for endpoint paths and route constants use the enums skill, and for process.env.* typing (non-null assertion vs fallback) use the type-safety skill.
author: Ivan Davidov
---
# Configuration
## Critical
- **NEVER** hardcode URLs, tokens, emails, or passwords anywhere in the scaffold. The only source of truth for env-driven values is `process.env.*`, backed by `env/.env.${environment}`.
- **NEVER** hardcode endpoint paths or route strings in `config/`. Paths belong in `enums/{area}/*` (see the `enums` skill).
- **ALWAYS** add every new env variable to `env/.env.example` with a safe placeholder — no real secrets, no production URLs.
- **ALWAYS** add a JSDoc comment on every config property describing the value and the backing env var.
- **ALWAYS** keep app-facing URLs/settings in `config/app.ts` and utility/third-party services in `config/util/util.ts`. Do not create ad-hoc config files elsewhere.
- **NEVER** commit `env/.env.dev`, `env/.env.staging`, or any other real env file containing credentials. Only `env/.env.example` is tracked.
## File Locations
| Type | Directory | Purpose |
| -------------- | ------------------------- | -------------------------------------------------------------- |
| App config | `config/app.ts` | URLs and settings for the main application |
| Utility config | `config/util/util.ts` | Utility / third-party service configuration |
| Env template | `env/.env.example` | Tracked template — safe placeholders only |
| Env (active) | `env/.env.${ENVIRONMENT}` | Real values, selected at runtime (`.env.dev` etc.) — untracked |
## Instructions
### Phase 1: Understand how env files are loaded
`playwright.config.ts` loads env values via `dotenv` at startup:
```typescript
const environment = process.env.ENVIRONMENT ?? 'dev';
const environmentPath = `./env/.env.${environment}`;
dotenv.config({ path: environmentPath });
```
Consequences:
- Default environment is `dev` (file `env/.env.dev`).
- Override with a shell variable: `ENVIRONMENT=staging npx playwright test`.
- The selected file must exist — missing files silently load nothing and every `process.env.*` becomes `undefined`.
- `ENVIRONMENT` itself is set at the **shell** level; it is not declared in any `.env` file.
### Phase 2: Decide where the new value belongs
Use this decision table before adding anything:
| Value kind | Home |
| ---------------------------------------------------------- | --------------------------------------------------------------------------- |
| URL of the main app under test | env var + `config/app.ts` (`appConfig.appUrl`, `apiUrl`) |
| URL of a utility / third-party service | env var + `config/util/util.ts` (`utilityConfig.*`) |
| Credential (email, password, API key, token seed) | env var only — **do not** expose through a config object |
| Endpoint path (e.g. `/api/users`) or route (e.g. `/login`) | `enums/{area}/*` — **not** `config/` and **not** an env var |
| Storage-state file path | `enums/{area}/*` (e.g. `StorageStatePaths`) — **not** `config/` |
| Timeout / retry / workers tuning | `playwright.config.ts` — **not** `config/` unless reused outside Playwright |
| Runtime selector (`ENVIRONMENT`, `CI`) | Shell-level env var only — **not** `.env.example` |
If the value fits none of the rows above, stop and ask — do not invent a new config file.
### Phase 3: Add the env var to `env/.env.example`
Every env var the scaffold relies on must appear in the tracked template with a **safe placeholder**:
```
APP_URL=https://your-app-url.com
API_URL=https://your-api-url.com
APP_EMAIL=your-email@example.com
APP_PASSWORD=your-secure-password
UTILITY_URL=https://your-utility-service.com
```
Rules for the placeholder:
- No real domains, no real tokens, no real passwords.
- Same key name as the eventual `process.env.*` lookup (exact case).
- Group related variables under a short comment header (see existing groupings).
Then add the real value to your active env file (`env/.env.dev` or similar) — this file is untracked.
### Phase 4: Add the config property (only if warranted) with JSDoc
Not every env var gets a config-object slot. Credentials (`APP_EMAIL`, `APP_PASSWORD`, tokens) stay env-only. URLs and infra settings that the scaffold wants to document **do** go into a config object.
**`config/app.ts`** — app-facing URLs and settings:
```typescript
/**
* Application configuration object.
* Contains URL configuration for the main application.
*
* For route paths and API endpoints, use enums from `enums/app/app.ts`.
*/
export const appConfig = {
/** Frontend application URL loaded from APP_URL env variable */
appUrl: process.env.APP_URL,
/** Backend API URL loaded from API_URL env variable */
apiUrl: process.env.API_URL,
};
```
**`config/util/util.ts`** — utility / third-party services:
```typescript
/**
* Utility service configuration object.
* Contains URL configuration for utility/helper services.
*
* For API endpoints, use enums from `enums/` folder.
*/
export const utilityConfig = {
/** Utility service base URL */
baseUrl: process.env.UTILITY_URL,
};
```
Every property requires a JSDoc comment naming the backing env var.
### Phase 5: Consume the value from tests, fixtures, and helpers
Two equally valid access patterns exist in the scaffold today:
1. **Direct `process.env.*` access** — the dominant pattern in tests, fixtures, and helpers:
```typescript
baseUrl: process.env.API_URL,
headers: process.env.ACCESS_TOKEN,
body: { email: process.env.APP_EMAIL, password: process.env.APP_PASSWORD },
```
2. **Config-object access** — used when you want to import a documented, organized surface:
```typescript
import { appConfig } from '../../config/app';
await page.goto(appConfig.appUrl!);
```
Pick whichever fits the call site; do not mix them inside a single file without reason. For how to satisfy the TypeScript `string | undefined` that `process.env.*` returns — non-null assertion (`!`) vs fallback default — see the `type-safety` skill.
## Examples
### Example 1: Add a new staging environment
User says: _"Add a staging environment pointing at the staging cluster."_
Actions:
1. **Phase 1** — Confirm `playwright.config.ts` already honors `ENVIRONMENT`; no code change needed.
2. Create `env/.env.staging` **locally** (untracked) with the real staging URLs/credentials, using the same keys as `env/.env.example`.
3. Run `ENVIRONMENT=staging npx playwright test` to verify the file loads.
4. **Do not** modify `env/.env.example` (the key names haven't changed) and **do not** commit `.env.staging`.
### Example 2: Add a new utility-service URL
User says: _"Wire up the reporting service URL so helpers can post run results."_
Actions:
1. **Phase 2** — It's a utility-service URL → belongs in `config/util/util.ts` as a property of `utilityConfig`; env var `REPORTING_URL`.
2. **Phase 3** — Add to `env/.env.example`:
```
# Optional: Additional service URLs
UTILITY_URL=https://your-utility-service.com
REPORTING_URL=https://your-reporting-service.com
```
3. **Phase 4** — Extend `config/util/util.ts`:
```typescript
export const utilityConfig = {
/** Utility service base URL */
baseUrl: process.env.UTILITY_URL,
/** Reporting service base URL loaded from REPORTING_URL env variable */
reportingUrl: process.env.REPORTING_URL,
};
```
4. **Phase 5** — Import `utilityConfig.reportingUrl` in the helper that posts results; follow the `type-safety` skill for `!` vs fallback handling.
## Troubleshooting
**`process.env.X` is `undefined` at runtime.**
Cause: The key is missing from the active env file, or the active env file doesn't exist.
Fix: Confirm the key exists in `env/.env.${ENVIRONMENT}` (default `env/.env.dev`). If you renamed or added a key, also update `env/.env.example`.
**Wrong environment is being loaded.**
Cause: `ENVIRONMENT` is unset, misspelled, or points at a missing file.
Fix: `playwright.config.ts` defaults to `dev`. Set `ENVIRONMENT=staging` in the shell (not in an `.env` file) before running tests; confirm `env/.env.staging` exists.
**I can't find `ACCESS_TOKEN` / `ACCESS_TOKEN_ZERO` in `env/.env.example`.**
Cause: These tokens are populated dynamically by an auth-bootstrap helper, not committed.
Fix: In the scaffold's demo setup, an auth-bootstrap helper under `helpers/{area}/` logs in and writes the token into `process.env.ACCESS_TOKEN` before the main suite runs. Do not add the token to `env/.env.example`. See the `helpers` skill (Phase 6) for the pattern.
**TypeScript complains that `process.env.X` is `string | undefined`.**
Cause: `process.env` values are always optional types in Node.
Fix: See the `type-safety` skill for the two sanctioned patterns (non-null assertion with `!` for values guaranteed to exist at runtime; fallback default with `??` otherwise).
**I want to put an endpoint path in a config file.**
Cause: Wrong skill. Paths are source-controlled constants, not environment-driven settings.
Fix: Add the path to `enums/{area}/*` (see the `enums` skill). `config/` is only for URLs, credentials, and infra settings.
**My new config property has no JSDoc and the PR review is blocking.**
Fix: Every config property requires a JSDoc comment naming the backing env var, e.g. `/** Reporting service base URL loaded from REPORTING_URL env variable */`.
**I accidentally committed `env/.env.dev`.**
Fix: Remove the file from the commit (`git rm --cached env/.env.dev`), verify it's covered by `.gitignore`, rotate any credentials that were exposed, and push the fix.
## See Also
- **`enums` skill** — endpoint paths, route constants, and storage-state paths live there, not in `config/`.
- **`type-safety` skill** — handling `string | undefined` returned by `process.env.*` (`!` vs fallback defaults).
- **`api-testing` skill** — which env vars tests consume (`API_URL`, `ACCESS_TOKEN`, `ACCESS_TOKEN_ZERO`, `APP_EMAIL`, `APP_PASSWORD`) and how.
- **`debugging` skill** — when `process.env.X` is `undefined` at runtime, when a CI run reads different env values than local, or when a navigation times out because `APP_URL` is wrong.