# 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`.

### In the Admin portal   {#portal}
When you deploy with `osy deploy`, the deploy reports each setting to the console: its value in each mode, and whether
the `.env.<mode>` file or the `Default` supplied it. An application's **Config** tab shows them **read-only**, per
environment it runs in — the value that environment reads (an application runs in one mode, so every environment reads
the same file, of the version deployed there) and **where it is set**: `.env.production`, or `Default in source`. There
is no edit box, on purpose: the repository is where a setting lives, and a value changed in the console would disagree
with it and lose to the next deploy. Change the file, and deploy.

A value that looks like a credential is **not reported** — your machine withholds it before the deploy request leaves,
and the tab shows it as *Not shown*, pointing at [declaring secrets (app.Secrets)](https://osysharp.com/reference/config/secrets/), where it belongs. So is any setting that shares its
name with a declared secret.

The same tab lists the hosts each package is granted (`use Acme.Snow@1 { egress "…"; }`), per environment, and an
**Open the app** button for each environment. Business settings — a shop's name, its prices, its fees — are your
application's own data, changed in the application itself; the console does not keep a copy of them.

Config values and egress grants are shown to whoever manages the application: an owner or admin of its organization,
or a member of the application who is not a Developer.

## 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)
- [The Llm kit — call a model, and know what each call cost](https://osysharp.com/reference/agent/llm-calls/) — `app.Llm`, the models an app declares to `Osysharp.Llm`
