# Configuration

> Typed settings that vary by environment

A config is a typed value your code reads, such as limits, timeouts and feature flags. You declare its type in Python and write its value in a TOML or JSON file beside the code, with overlays that change it for some environments only. Encore checks every value against the type before your service starts, and under `encore run` you can change a value without restarting anything.

> **Important**
>
> For sensitive data, use [secrets](/docs/py/primitives/secrets) instead. A config can [read secrets](#secrets-in-config) but never holds them in its files.

## Declaring a config

Describe the config's value with a dataclass, and declare the config as a module-level variable:

```python
-- orders/settings.py --
from dataclasses import dataclass, field
from datetime import timedelta
from typing import Annotated

from encore.config import Config, Max, Min


@dataclass
class Checkout:
    max_items: Annotated[int, Min(1), Max(500)]
    currencies: list[str] = field(default_factory=lambda: ["USD"])
    payment_timeout: timedelta = timedelta(seconds=90)


checkout = Config[Checkout]("checkout")
```

Then write its value in a file named after it, beside the service's `encore.service.json`:

```toml
-- orders/checkout.config.toml --
max_items = 100
payment_timeout = "2m"
```

Read the value with `.value`. It's an instance of your type:

```python
from orders.settings import checkout


def validate_cart(items: list[str]) -> None:
    limit = checkout.value.max_items
    if len(items) > limit:
        raise ValueError(f"at most {limit} items per order")
```

Encore reads config declarations from the source, so:

- Declare configs at module level.
- The name must be a string literal and an identifier: a letter or underscore, then letters, digits and underscores.
- The type argument is required: `Config[Checkout]("checkout")`. Without it, reading `.value` raises `TypeError`.
- Use the config by reading `.value` or calling `.on_change()`. Encore records which services read which configs from these calls, so it rejects passing the config object itself around.

## Supported types

The config's type is a dataclass, a `TypedDict` or an `Enum`, built from:

- `str`, `int`, `float`, `bool`
- `Literal[...]`, `Optional[...]`, `Enum` subclasses
- `list[...]` and `dict[str, ...]`
- nested dataclasses and `TypedDict`s, including generic ones
- `typing.Any`
- `datetime.timedelta`, written as a duration string such as `"30s"`, `"250ms"` or `"1h30m"`
- [`Secret`](#secrets-in-config) fields

A dataclass field with a default, or an `Optional` field, may be left out of the files. A union of dataclasses is supported when each has a `Literal` tag field that tells them apart. Encore reports a type it can't describe as an error at the part of the type that's the problem.

### Constraints

Narrow what a field accepts with `Annotated` markers from `encore.config`:

| Marker | Applies to | Example |
| --- | --- | --- |
| `Min(n)`, `Max(n)` | `int`, `float` | `Annotated[int, Min(1), Max(500)]` |
| `MinLen(n)`, `MaxLen(n)` | `str` | `Annotated[str, MaxLen(64)]` |
| `Pattern(regex)` | `str` | `Annotated[str, Pattern("^[A-Z]+$")]` |
| `MinItems(n)`, `MaxItems(n)` | `list` | `Annotated[list[str], MinItems(1)]` |

Encore checks values against these before the service sees them. At run time the markers do nothing.

## Config scopes

As with secrets, the class you declare a config with decides who it belongs to:

| Class | Belongs to | Its files live |
| --- | --- | --- |
| `Config[T]` | The service that declares it, which has its own value. Must be declared inside a service. | Beside the service's `encore.service.json` |
| `SharedConfig[T]` | The whole app, with one value. Encore records each service that reads it. | Beside `encore.app` |
| `GlobalConfig[T]` | The whole app, with one value, available to every service. Reads are not recorded. | Beside `encore.app` |

```python
from dataclasses import dataclass

from encore.config import SharedConfig


@dataclass
class Flags:
    new_checkout: bool = False


flags = SharedConfig[Flags]("flags")
```

## Environment-specific values

A config's value is merged from several files. Each layer overrides the ones before it, using [JSON Merge Patch](https://www.rfc-editor.org/rfc/rfc7396) semantics:

1. The defaults in your type.
2. `<name>.config.toml`: the value in every environment.
3. `<name>.<type>.config.toml`: environments of one type, where the type is `local` (`encore run`), `test` (`encore test`), `development`, `preview` or `production`.
4. `<name>.@<env>.config.toml`: the environment named `<env>`.
5. `<name>.user.config.toml`: your own overrides, applied to your `encore run` only. These files are gitignored (`*.user.config.*`) and never read in tests.

Each file can be `.toml` or `.json`. For example:

```toml
-- orders/checkout.config.toml --
max_items = 100
```

```toml
-- orders/checkout.production.config.toml --
max_items = 500
payment_timeout = "30s"
```

```toml
-- orders/checkout.@prod-eu.config.toml --
currencies = ["EUR", "SEK"]
```

Each file is checked as a partial value, so a mistake is reported in the file that makes it, and the merged value is then checked in full. A file whose name doesn't match a declared config is ignored, unless its name is close to one, which is reported as a likely typo.

## Reacting to changes

Under `encore run`, editing a config file updates the value in running services without a rebuild or restart. An invalid edit is reported, and services keep the last valid value.

Each read of `.value` returns the newest value. Keep the result in a local variable when you want one consistent value for the duration of an operation:

```python
settings = checkout.value  # one snapshot for this whole request
```

To act when the value changes, register a callback with `on_change`. It runs on a background thread, so keep it quick. It returns a function that stops the calls:

```python
import logging

log = logging.getLogger(__name__)


def log_change(value: Checkout) -> None:
    log.info("checkout config changed", extra={"max_items": value.max_items})


stop = checkout.on_change(log_change)
```

An exception raised by the callback is logged, and later changes still reach it.

## Secrets in config

A field of type `Secret[str]` or `Secret[bytes]` reads a secret instead of holding a value. Its default names the secret. The secret must also be declared in code, with the config's scope: a service's own `Secret` for a `Config`, a `SharedSecret` for a `SharedConfig`, and a `GlobalSecret` for a `GlobalConfig`.

```python
-- orders/settings.py --
from dataclasses import dataclass

from encore.config import Config
from encore.secrets import Secret

stripe_key = Secret[str]("StripeKey")


@dataclass
class Payments:
    provider_url: str
    api_key: Secret[str] = Secret[str]("StripeKey")


payments = Config[Payments]("payments")


def auth_header() -> str:
    return f"Bearer {payments.value.api_key.value}"
```

The field's type is always `Secret`, whatever the config's scope. Secret fields can't be written in config files, and can't be `Optional` or be inside lists, dicts or unions.

## Inspecting values with the CLI

The `encore config` commands show and edit config values:

```shell
$ encore config list                                    # the app's configs, and whether each value is valid
$ encore config show orders.checkout                    # the merged value, and which file each part comes from
$ encore config show orders.checkout --env-type production
$ encore config validate                                # check every file, for every environment type
$ encore config schema orders.checkout                  # the JSON Schema Encore derived from the type
```

Name a service's config as `<service>.<name>`, and a shared or global one by its name alone.

`encore config set` and `encore config unset` change one value in one file, keeping the rest of the file as it was. They write your user override by default; pass `--base`, `--env-type <type>` or `--env <name>` to change another layer:

```shell
$ encore config set orders.checkout max_items 20
$ encore config set orders.checkout max_items 500 --env-type production
$ encore config unset orders.checkout max_items
```

## Editor support

Run `encore config link` to name each config file's schema in the file: a `#:schema` comment in TOML, or a `"$schema"` key in JSON. Editors with JSON Schema support then check and complete what you write. The schemas are served by the local Encore daemon. Run it again after adding a file.

## Testing

Under `encore test`, a config has the value its `test` overlay gives it: `<name>.test.config.toml` over the base file. User overrides are never applied in tests.
