December 11, 2025·5 min read·engineeringswiftconfigurationlibrariespackages

Swift Configuration 1.0 separates lookups from provider policy

Swift Configuration 1.0 gives libraries a common reader API while applications choose provider order. Defaults and package traits make the resulting configuration contract more explicit.

A reusable library should not have to decide whether its timeout comes from a process environment or a configuration file. That decision belongs to the application assembling the library. When every dependency reads its preferred source directly, changing deployment policy can require changes throughout the program. Swift Configuration introduces a shared reader interface so those decisions can remain at the application boundary.

Honza Dvorsky announced Swift Configuration 1.0 on December 11. The release establishes a stable API after the initial October announcement. Applications select providers, while libraries consume a reader without requiring the same source everywhere. The package includes several built-in sources and a public protocol for additional integrations.

I appreciate this separation in Swift because it makes a library's input contract more explicit. A timeout is a value the library needs. The location and precedence of the value are application decisions. Combining those responsibilities makes a small convenience harder to reuse when the program's environment changes.

Provider order determines the result

The 1.0.0 README demonstrates an environment provider ahead of a JSON file provider. Earlier providers have higher priority. A default attached to the lookup applies after the providers fail to supply the value.

A configuration file can define a timeout under a nested key. Here the nested object supplies the normal application baseline value:

{
  "http": {
    "timeout": 30
  }
}

The reader can layer the environment before that file. Its lookup uses the same logical key regardless of which source supplies the value, so the consuming code does not need to branch on deployment environment:

import Configuration

let config = ConfigReader(providers: [
    EnvironmentVariablesProvider(),
    try await FileProvider<JSONSnapshot>(filePath: "/etc/config.json")
])
let timeout = config.int(forKey: "http.timeout", default: 60)

With HTTP_TIMEOUT=15 in the environment, the documented result is 15. Without that override, the JSON value supplies 30. If neither provider supplies it, the lookup returns the fallback of 60. These values demonstrate the precedence rule, rather than requiring a library to encode the provider sequence itself.

The distinction between a file default and a lookup default is useful. A file can hold the application's normal configuration shared across many components. A lookup fallback belongs to the particular access. Keeping both visible lets an application express a deployment-wide baseline without removing the library's behavior when the key is absent.

A reader is an integration point for libraries

The reader API permits an application to assemble its sources once and pass configuration to dependencies. A library can request an integer under a documented key without importing the code for every provider the application might eventually use. That reduces coupling between the component interpreting a value and the component retrieving it.

The release announcement describes integrations under development in Vapor, Hummingbird, and the Swift Temporal SDK. These are examples of work in progress, not a declaration that every project has already shipped a stable integration. The 1.0 milestone gives those projects a stable reader API on which to build.

This boundary also makes alternative sources possible without changing every consumer. The public ConfigProvider protocol lets another package implement a file format or remote service integration. The announcement names experimental providers for TOML, Vault, and AWS. Their experimental status is separate from the stability of the core library.

That distinction prevents a common misunderstanding of a configuration abstraction. A stable reader does not guarantee that every external service integration has the same maturity or behavior. The application still chooses its providers. The shared API lets that choice change without redesigning the library's configuration entry point.

Package traits control additional integrations

Swift Configuration uses package traits to make several integrations optional. JSON support is enabled by default in 1.0.0. YAML support, access logging, command-line argument parsing, and file reloading have opt-in traits. An application should therefore distinguish the capabilities described by the project from the capabilities enabled in its own dependency declaration.

The README shows enabling YAML while retaining the package's defaults. This dependency declaration requests both, leaving the default JSON capability available alongside the additional YAML integration:

.package(
    url: "https://github.com/apple/swift-configuration",
    from: "1.0.0",
    traits: [.defaults, "YAML"]
)

The declaration makes that additional source format visible in the build configuration. Selecting a provider type in source also requires the integration that defines it. A reader abstraction does not make every optional provider available without its corresponding package configuration.

Reloading has another explicit condition. The Reloading trait adds reloading file-provider variants, while a JSON or YAML variant also requires its format support. These are separate capabilities that compose. A package using a static JSON file does not need to imply that the file will be watched for changes.

Update behavior is a consumer decision

The release describes synchronous, asynchronous, and watching access patterns. It also includes hot reloading, scoped readers, and access logging. These options address different ways a component can consume configuration, so adopting the reader does not force every setting into a single lifetime.

A value read during initialization can determine how a component is constructed. A watched value can instead support a component that responds to later changes. Those application designs require different behavior even when the initial value comes from the same provider. Provider availability alone cannot determine whether rebuilding a component or updating it in place is appropriate.

I regard the stable reader as most useful when that policy stays explicit. A library should make clear which configuration values it reads and when it reads them. Otherwise the application can choose a reloading source and still misunderstand which already-created objects will react to an update.

The package also includes secret redaction and access logging for diagnosing configuration use. Redaction matters when observing where a value came from could otherwise expose its contents. These facilities support investigation around the reader, while provider selection and the handling of retrieved values remain responsibilities of the application.

The 1.0 release supports Apple platforms and Linux according to its versioned README. Its stable API gives package authors a common configuration entry point within those environments. The concrete contract consists of the keys a consumer reads, the provider order the application supplies, and the fallback behavior declared at each lookup.