---
title: Auto-Discovery
description: Automatically discover configuration files in monorepos and multi-application projects.
icon: FolderSearch
---

Confy can automatically find your configuration files by searching the current directory and parent directories. This is especially useful in monorepos where your application may be nested several directories deep.

## Quick Start

```go
cfg, err := confy.AutoLoadConfy("myapp", nil)
if err != nil {
    log.Fatal(err)
}

port := cfg.GetInt("server.port", 8080)
```

`AutoLoadConfy` uses default settings to search for `config.yaml` and `config.local.yaml` starting from the current working directory, walking up parent directories.

## Discovery Configuration

For more control, use `DiscoverAndLoadConfigs` with an `AutoDiscoveryConfig`:

```go
cfg, result, err := confy.DiscoverAndLoadConfigs(confy.AutoDiscoveryConfig{
    AppName:          "myapp",
    SearchPaths:      []string{".", "./config", "/etc/myapp"},
    ConfigNames:      []string{"config.yaml", "config.yml", "config.json"},
    LocalConfigNames: []string{"config.local.yaml", "config.local.yml"},
    MaxDepth:         5,
    RequireBase:      true,
    EnableEnvSource:  true,
    EnvPrefix:        "MYAPP",
    EnvSeparator:     "_",
    EnvOverridesFile: true,
})

if err != nil {
    log.Fatal(err)
}

// Discovery result contains useful metadata
fmt.Println("Base configs:", result.BaseConfigPaths)
fmt.Println("Local configs:", result.LocalConfigPaths)
fmt.Println("Working dir:", result.WorkingDirectory)
fmt.Println("Monorepo:", result.IsMonorepo)

// Convenience methods return the first path (backward compat)
fmt.Println("First base:", result.BaseConfigPath())
fmt.Println("First local:", result.LocalConfigPath())
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `AppName` | `string` | `""` | Application name (used for app-scoping) |
| `SearchPaths` | `[]string` | `["."]` | Directories to search |
| `ConfigNames` | `[]string` | `["config.yaml"]` | Base config filenames |
| `LocalConfigNames` | `[]string` | `["config.local.yaml"]` | Local override filenames |
| `MaxDepth` | `int` | `5` | Max parent directories to search |
| `RequireBase` | `bool` | `false` | Error if base config not found |
| `RequireLocal` | `bool` | `false` | Error if local config not found |
| `EnableAppScoping` | `bool` | `false` | Extract app-scoped config |
| `EnableEnvSource` | `bool` | `false` | Add environment source |
| `EnvPrefix` | `string` | `""` | Env variable prefix |
| `EnvSeparator` | `string` | `"_"` | Env key separator |
| `EnvOverridesFile` | `bool` | `true` | Env vars override file values |

## Search Algorithm

The discovery process searches **all** configured search paths and merges every config it finds:

1. For each search path, look for any of the `ConfigNames` files
2. If not found, walk up to the parent directory (up to `MaxDepth` levels)
3. Once found, also look for `LocalConfigNames` in the same directory
4. **Repeat for every remaining search path** — all paths are searched, not just the first match
5. Duplicate paths are removed automatically
6. Optionally add an environment source

```
project-root/
├── config.yaml          ← discovered (base, priority 100)
├── config.local.yaml    ← discovered (local, priority 200)
├── services/
│   └── api/
│       └── main.go      ← working directory (cwd)
```

### Multi-path merging

When multiple search paths each contain config files, they are all loaded with incrementing priorities so later paths layer on top of earlier ones:

```
SearchPaths: ["./config", "/etc/myapp"]

./config/config.yaml       → base priority 100
/etc/myapp/config.yaml     → base priority 101 (overrides ./config values)
./config/config.local.yaml → local priority 200
/etc/myapp/config.local.yaml → local priority 201 (overrides ./config local)
```

All local configs override all base configs, and environment variables (priority 300 by default) override everything.

## App-Scoped Configuration

In monorepos with multiple applications sharing a single config file, use app-scoping to extract the relevant section:

```yaml
# config.yaml (shared)
database:
  host: shared-db.internal

apps:
  api:
    port: 8080
    database:
      host: api-db.internal

  worker:
    concurrency: 10
    database:
      host: worker-db.internal
```

```go
cfg, err := confy.LoadConfigWithAppScope("api", logger, nil)
if err != nil {
    log.Fatal(err)
}

// Gets "api-db.internal" — app config overrides shared config
host := cfg.GetString("database.host")

// Gets 8080 — from the api app section
port := cfg.GetInt("port")
```

### How App-Scoping Works

1. Load the full configuration file
2. Find the `apps.<appName>` section
3. Merge the app-specific config over the global config
4. App keys override global keys at the same path

### Priority Resolution

When using app-scoping with multiple sources:

```yaml
# config.yaml (priority 100)
database:
  host: base-host
apps:
  api:
    database:
      host: app-host

# config.local.yaml (priority 200)
database:
  host: local-host
```

For app `api`, the resolution is:
1. `base-host` from base config (priority 100)
2. `app-host` from app-scoped base config
3. `local-host` from local config (priority 200) — **wins**

Local config globals always override app-scoped base config.

## Loading from Explicit Paths

When you know exactly where your config files are:

```go
cfg, err := confy.LoadConfigFromPaths(
    "/etc/myapp/config.yaml",       // base path
    "/etc/myapp/config.local.yaml", // local path
    "api",                          // app name (optional)
    logger,
)
```

## Debugging Discovery

Use `GetConfigSearchInfo` to understand where confy would look for configs:

```go
info := confy.GetConfigSearchInfo("myapp")
fmt.Println(info)
// Outputs search paths, filenames, and priority information
```

## Default Discovery Configuration

`DefaultAutoDiscoveryConfig()` returns sensible defaults:

```go
defaultCfg := confy.DefaultAutoDiscoveryConfig()
// SearchPaths: ["."]
// ConfigNames: ["config.yaml", "config.yml"]
// LocalConfigNames: ["config.local.yaml", "config.local.yml"]
// MaxDepth: 5
// EnvSeparator: "_"
// EnvOverridesFile: true
```

<Callout type="info">
  The `config.local.yaml` file is typically added to `.gitignore` so each developer can maintain their own overrides without affecting the shared configuration.
</Callout>
