> ## Documentation Index
> Fetch the complete documentation index at: https://docs.talosjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Configure your application with environment variables and roles, both declared in YAML and loaded at boot.

An Talos application is configured through two YAML files in the `shared` module. `modules/shared/.env.yml` holds your environment variables: database URLs, API keys, ports, CORS, and per-environment access lists. `modules/shared/src/roles.yml` holds the role hierarchy your [controllers](/controller) check for access control.

Both are generated by [`app:create`](/cli/commands/app-create) and loaded automatically when the app boots. This page covers how to edit them and read their values in code.

Alongside them, `app:create` writes a set of project-tooling files at the repository root: commit conventions, code style, TypeScript, and editor settings. They come scaffolded with defaults that work as-is, and the sections at the end of this page describe each one and how to tune it.

## Environment variables

Environment is configured in YAML rather than a flat `.env` file. At boot, [`@talosjs/app-env`](/components/auth) reads `modules/shared/.env.yml`, flattens its nested keys into uppercase environment variables (`app.port` → `PORT`, `database.url` → `DATABASE_URL`, `ai.anthropic.api_key` → `ANTHROPIC_API_KEY`), and exposes them through a typed, injectable `AppEnv` class. Empty values are skipped, so unconfigured services stay unset.

### Installation

Included in every scaffolded project. To add it manually:

```bash theme={null}
bun add @talosjs/app-env
```

### How to use

Edit `modules/shared/.env.yml`. The file is organized into sections, one per concern. Fill in the services you use and leave the rest empty.

```yaml modules/shared/.env.yml theme={null}
app:
  env: "development" # development | staging | production | local | ...
  host: "127.0.0.1"
  port: 3000

database:
  url: "postgresql://user:password@localhost:5432/movie_app"

cache:
  redis:
    url: "redis://localhost:6379"

jwt:
  secret: "change-me-in-production"

mailer:
  sender:
    name: "Movie App"
    address: "noreply@movie-app.com"
  resend:
    api_key: ""

ai:
  anthropic:
    api_key: "sk-ant-..."

# Comma-separated emails granted elevated roles per environment
allowed_users:
  development: "dev@example.com,qa@example.com"
  super_admin: "founder@example.com"
  admin: "admin@example.com"
```

The `app.env` value drives environment detection. Supported values include `local`, `development`, `staging`, `testing`, `qa`, `uat`, `preview`, `demo`, `sandbox`, `beta`, `canary`, `hotfix`, and `production`.

<Note>
  Treat `.env.yml` as a secret. Keep real credentials out of version control and
  inject them per environment in CI/CD and production.
</Note>

### Reading values in code

`AppEnv` is registered in the container at boot. Inject it into any class with `@inject` and read its typed properties:

```typescript theme={null}
import { AppEnv } from "@talosjs/app-env";
import { inject } from "@talosjs/container";
import { decorator } from "@talosjs/service";

@decorator.service()
export class CatalogService {
  constructor(@inject(AppEnv) private readonly env: AppEnv) {}

  public async execute() {
    this.env.APP_ENV; // "development"
    this.env.PORT; // 3000
    this.env.DATABASE_URL; // "postgresql://..."
    this.env.isProduction; // boolean
    this.env.isLocal; // boolean
  }
}
```

Inside a [controller](/controller), the same values are available on the context as `context.env`.

## Roles

Roles are an access-control hierarchy declared in `modules/shared/src/roles.yml` and enforced by [`@talosjs/role`](/components/permission). Each role can inherit from a parent, so a user with `ROLE_ADMIN` automatically satisfies any route requiring `ROLE_USER`. A [controller](/controller) restricts access by listing roles on its route (`roles: ["ROLE_USER"]`), and the framework rejects requests from users who lack a matching (or inheriting) role with `403 Forbidden`.

At boot the app validates the file and registers it in the container. In local development, it also regenerates `roles.types.ts` so role names are type-checked.

### Installation

Included in every scaffolded project. To add it manually:

```bash theme={null}
bun add @talosjs/role
```

### How to use

Edit `modules/shared/src/roles.yml`. The `roles` section maps friendly names to role keys, and the `hierarchy` section declares each role's inheritance and description.

```yaml modules/shared/src/roles.yml theme={null}
roles:
  GUEST: ROLE_GUEST
  USER: ROLE_USER
  PREMIUM_USER: ROLE_PREMIUM_USER
  MODERATOR: ROLE_MODERATOR
  ADMIN: ROLE_ADMIN
  SUPER_ADMIN: ROLE_SUPER_ADMIN

hierarchy:
  ROLE_GUEST:
    description: Unauthenticated visitor with read-only access to public content

  ROLE_USER:
    inherits: [ROLE_GUEST]
    description: Standard authenticated user with full access to core features

  ROLE_PREMIUM_USER:
    inherits: [ROLE_USER]
    description: Paid subscriber with access to premium features

  ROLE_MODERATOR:
    inherits: [ROLE_USER]
    description: Community moderator who can manage posts and reports

  ROLE_ADMIN:
    inherits: [ROLE_MODERATOR]
    description: Administrator with full control over users and content

  ROLE_SUPER_ADMIN:
    inherits: [ROLE_ADMIN]
    description: Super administrator with unrestricted access
```

The default scaffold ships a richer hierarchy (`ROLE_TRIAL_USER`, `ROLE_VIP_USER`, `ROLE_REVIEWER`, `ROLE_MANAGER`, `ROLE_SYSTEM`, …). Keep what you need and prune the rest. Whatever you keep, always pick the **least-privileged** role that satisfies an endpoint.

### Restricting a route

List the required role on the controller's route. Inheritance means higher roles pass automatically:

```typescript theme={null}
@Route.get("/movies", {
  name: "movie.list",
  version: 1,
  description: "List movies",
  roles: ["ROLE_USER"], // ROLE_ADMIN, ROLE_SUPER_ADMIN, … also pass
})
export class MovieListController {
  public async index(context: ContextType<MovieListRouteType>) {
    const user = context.user; // the authenticated user and its roles
    return context.response.json({
      /* ... */
    });
  }
}
```

### Granting roles by email

The `allowed_users` section of `.env.yml` grants elevated roles to specific emails per environment. When an authenticated user's email matches, the framework adds the corresponding role at request time. This is handy for seeding the first `ADMIN` or `SUPER_ADMIN` without a database migration:

```yaml modules/shared/.env.yml theme={null}
allowed_users:
  super_admin: "founder@example.com"
  admin: "admin@example.com,ops@example.com"
```

## Commit conventions

Commits are linted with [commitlint](https://commitlint.js.org/) so history stays consistent and machine-readable. `app:init` generates `.commitlintrc.ts` at the project root, wires it into [Husky](https://typicode.github.io/husky/) Git hooks, and runs [lint-staged](https://github.com/lint-staged/lint-staged) on staged files before each commit.

Three pieces work together. `.husky/pre-commit` runs `lint-staged`, which runs Biome over staged `.js/.ts/.jsx/.tsx/.json/.jsonc` files. `.husky/commit-msg` runs `bunx commitlint --edit "$1"` to validate the message. The rules themselves, along with the interactive `bunx commit` prompt, live in `.commitlintrc.ts`.

### How to use

Write [Conventional Commits](https://www.conventionalcommits.org/) in the form `type(scope): subject`. The config enforces the allowed types and scopes:

```typescript .commitlintrc.ts theme={null}
import { RuleConfigSeverity, type UserConfig } from "@commitlint/types";

const Configuration: UserConfig = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      RuleConfigSeverity.Error,
      "always",
      ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"],
    ],
    "scope-enum": [
      RuleConfigSeverity.Error,
      "always",
      ["common", "shared", "app"],
    ],
    "scope-empty": [RuleConfigSeverity.Error, "never"],
    "subject-empty": [RuleConfigSeverity.Error, "never"],
    "header-max-length": [RuleConfigSeverity.Error, "always", 100],
    // ...
  },
};

export default Configuration;
```

A scope is **required** and must be one of `common`, `shared`, or `app`. Add a new module's name to `scope-enum` when you create it, so commits like `feat(billing): add invoices` pass. Multiple scopes are allowed (`feat(app, shared): ...`).

Instead of `git commit`, you can run the interactive prompt:

```bash theme={null}
bun run commit
```

It walks you through the type, scope, and subject and produces a compliant message.

## Code style

Formatting and linting are handled by [Biome](https://biomejs.dev/), a single fast tool that replaces ESLint and Prettier. `app:init` writes `biome.jsonc` at the project root.

### How to use

Format and auto-fix the whole project, or lint without writing:

```bash theme={null}
bun run fmt    # biome check --write
bun run lint   # runs lint across every module
```

The generated `biome.jsonc` enables both the formatter and a strict linter, and turns on import organization:

```jsonc biome.jsonc theme={null}
{
  "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
  "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 120,
    "lineEnding": "lf"
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "suspicious": {
        "noExplicitAny": "error",
        "noConsole": "error"
      },
      "style": {
        "useImportType": "error",
        "useTemplate": "error"
      }
    }
  },
  "javascript": {
    "formatter": { "quoteStyle": "double" },
    "parser": { "unsafeParameterDecoratorsEnabled": true }
  },
  "assist": {
    "enabled": true,
    "actions": { "source": { "organizeImports": "on" } }
  }
}
```

A few defaults worth knowing. Formatting is 2-space indent, 120-column lines, LF endings, and double quotes. `noExplicitAny` and `noConsole` are set to **errors**, so the linter rejects `any` and stray `console.*` calls. `unsafeParameterDecoratorsEnabled` is on so the framework's `@inject(...)` parameter decorators parse, and imports are organized automatically when you format.

Adjust any rule by editing `biome.jsonc`; see the [Biome rules reference](https://biomejs.dev/linter/rules/) for the full list.

## TypeScript

`tsconfig.json` at the project root sets strict compiler options and the module path aliases used across the app.

### How to use

The defaults are strict and Bun-targeted, and most projects never touch them:

```jsonc tsconfig.json theme={null}
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "jsx": "react-jsx",
    "paths": {
      "@module/app/*": ["./modules/app/src/*"],
      "@module/shared/*": ["./modules/shared/src/*"]
    },
    "types": ["bun"]
  },
  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules", ".github", ".husky", ".nx", ".zed", ".vscode"]
}
```

A few things to note. `experimentalDecorators` and `emitDecoratorMetadata` are required for the framework's `@decorator.*` and `@inject` decorators. The `paths` aliases let you import across modules with `@module/<module>/...` instead of relative paths, so **add an entry for each new module** to keep its imports resolving. Strict flags like `noUncheckedIndexedAccess` and `noUnusedLocals` are on by default; keep them on for the strongest type safety.

## Editor (Zed)

`app:init` writes `.zed/settings.json` so the [Zed editor](https://zed.dev/) formats and fixes with Biome on save, with no extra setup.

### How to use

Open the project in Zed and you're done. The generated settings make Biome the formatter for JS, TS, TSX, JSON, JSONC, CSS, Astro, and Svelte, and run Biome's fix + organize-imports actions on every save:

```json .zed/settings.json theme={null}
{
  "formatter": "language_server",
  "format_on_save": "on",
  "languages": {
    "TypeScript": {
      "formatter": { "language_server": { "name": "biome" } },
      "code_actions_on_format": {
        "source.fixAll.biome": true,
        "source.organizeImports.biome": true
      }
    }
    // ...same for JavaScript, TSX, JSON, JSONC, CSS, Astro, Svelte
  }
}
```

This mirrors the `bun run fmt` behavior on save, so files are formatted and imports organized as you work. Using a different editor? Point its Biome integration at `biome.jsonc` for the same result: VS Code via the [Biome extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome), or any editor with an LSP client.

## Use with Claude and Codex

Initialize the AI skills, then ask your agent to wire configuration in natural language. It edits the YAML files and uses the project's actual roles.

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    talos claude:init
    ```

    ```text Prompt icon="terminal" wrap theme={null}
    Configure the app for local development: set the Postgres and Redis URLs
    in .env.yml, add a PREMIUM_USER role inheriting from USER in roles.yml,
    and grant admin@example.com the ADMIN role.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    talos codex:init
    ```

    ```text Prompt icon="terminal" wrap theme={null}
    Configure the app for local development: set the Postgres and Redis URLs
    in .env.yml, add a PREMIUM_USER role inheriting from USER in roles.yml,
    and grant admin@example.com the ADMIN role.
    ```
  </Tab>
</Tabs>

## External resources

<CardGroup cols={2}>
  <Card title="YAML Spec" icon={<Icon icon="file-code" size={16} />} href="https://yaml.org/spec/">
    The format both configuration files use.
  </Card>

  <Card title="PostgreSQL Connection URLs" icon={<Icon icon="database" size={16} />} href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">
    Format for the `database.url` value.
  </Card>

  <Card title="Redis" icon={<Icon icon="bolt" size={16} />} href="https://redis.io/docs/latest/">
    Cache, queue, and pub/sub connection URLs.
  </Card>

  <Card title="The Twelve-Factor App: Config" icon={<Icon icon="gear" size={16} />} href="https://12factor.net/config">
    Why configuration lives in the environment.
  </Card>
</CardGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Controller" icon={<Icon icon="route" size={16} />} href="/controller">
    Use the roles you defined to restrict your endpoints.
  </Card>

  <Card title="Auth component reference" icon={<Icon icon="lock" size={16} />} href="/components/auth">
    The full `@talosjs/app-env` environment API.
  </Card>

  <Card title="Permission component reference" icon={<Icon icon="user-shield" size={16} />} href="/components/permission">
    Fine-grained access checks beyond roles.
  </Card>

  <Card title="Create your app" icon={<Icon icon="rocket" size={16} />} href="/getting-started/create-app">
    Where these files are generated.
  </Card>
</CardGroup>
