# per-environment config (app.Config)

> `app.Config` declares your app's per-environment settings — values that differ between development and production, like an invite base URL or a from-address. Each is `new Setting("Name")` with an optional `Default`; the real per-environment values come from checked-in `.env.development` / `.env.production` files. Read a setting anywhere with the `Config.Name` handle — in a function body and in a component — and it resolves to the value for the environment the app is running in.

<!-- id: config-app-config · area: config · stability: stable · html: https://osysharp.com/reference/config/app-config/ -->

## Summary        {#summary}
`app.Config` declares the **per-environment settings** your application uses — non-secret values that differ between
development and production, such as an invite base URL (`http://localhost:8099` locally, `https://app.example.com` in
production) or a from-address. It is the **non-secret twin of [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/)**: you declare a setting's **name** in
source and supply its per-environment **values** out of band, but — unlike a secret — a setting's value is public and is
**readable everywhere** through the `Config.Name` handle, in a function body and in a component alike.

```osy syntax
app.Config = [
  new Setting("InviteBaseUrl") { Default = "http://localhost:8099" },
  new Setting("FromAddress")   { Default = "noreply@localhost" },
];

// read it anywhere by handle — it resolves to the running environment's value:
string invite = Config.InviteBaseUrl + "/accept?token=" + token;
```

## Signature      {#signature}
```osy syntax
app.Config = [                                       // one entry per named setting
  new Setting("Name") { Default = "…" },             // Default: the value used when no .env supplies one
  new Setting("Name"),                               // no Default: the value is REQUIRED from a .env file
];

Config.Name                                          // the setting's value for the running environment (a string)
```

`app.Config` is a list — an app may declare as many named settings as it needs.

## Description    {#description}
A setting has a **name** (a string literal, the `Config.Name` read key) and an optional **`Default`**. The name and the
`Default` are the only things that live in source. The actual per-environment values come from two checked-in files that
sit next to your `app.osy`:

- **`.env.development`** and **`.env.production`** — plain `KEY=VALUE` files (`#` comments and blank lines ignored).
  A key names a setting; its value is that setting's value for that environment.

These `.env` files are **compile inputs**, not runtime reads: when you compile, both value sets are baked into the app,
so one compiled app carries its development *and* its production values and needs no per-environment recompile. For each
setting, the value in each environment is the `.env` override if present, otherwise the setting's `Default`.

At runtime the app **selects** the set matching the environment it is running in — a local dev server resolves the
development values, a production deployment resolves the production ones — so `Config.InviteBaseUrl` reads
`http://localhost:8099` locally and `https://app.example.com` in production, from the same build.

**Reading a setting.** `Config.Name` is a plain string value. Use it in a function body (building an email link,
choosing a from-address) and in a component (a link, a label). Reading a setting that is not declared in `app.Config` is
a compile error — a typo never silently reads as empty.

**Required vs. defaulted.** A setting with a `Default` always has a value. A setting **without** a `Default` is
*required*: if either `.env.development` or `.env.production` does not supply it, the compile fails — you cannot ship a
build that is missing a value in an environment it targets. An `.env` key that matches no declared setting is likewise a
compile error, so a stale or mistyped key is caught rather than silently ignored.

**Not for secrets.** `app.Config` values are stored and served in the clear — they are meant to be public (a URL, an
address), and a component can read them. An API key, token, or password belongs in [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/), which keeps its
value out of source and never sends it to a browser. A config value that looks like a secret (a long random token, an
`sk-…` key) is flagged with a warning nudging you toward `app.Secrets`.

## Examples       {#examples}
Declare two settings with development defaults, and read one in a function that builds an invite link:

```osy title="basic" test app=config-app-config-example
app.Config = [
  new Setting("InviteBaseUrl") { Default = "http://localhost:8099" },
  new Setting("FromAddress")   { Default = "noreply@localhost" },
];

string InviteLink(string token) {
  return Config.InviteBaseUrl + "/accept?token=" + token;
}
```

To give production a different value, add a checked-in `.env.production` next to your `app.osy`:

```text title=".env.production"
InviteBaseUrl=https://app.example.com
FromAddress=hello@example.com
```

Now `Config.InviteBaseUrl` reads `http://localhost:8099` when the app runs locally and `https://app.example.com` when it
runs in production — from one compiled build, with no code change.

## See also       {#see-also}
- [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/) — the SECRET twin: a value kept out of source and never served to the client
- [embedding provider (app.Embedding)](https://osysharp.com/reference/config/embedding/) — another `app.X` config block (the embedding model)
- [default LLM model (app.DefaultModel)](https://osysharp.com/reference/agent/default-model/) — `app.DefaultModel`, the app's default LLM config
