In this blog post How to Use .NET appsettings.json for Cleaner Configuration we will explore how appsettings.json helps you manage configuration in modern .NET apps, without sprinkling values throughout your codebase. You will learn what it is, why it exists, and how to use it confidently in real-world projects.
At a high level, appsettings.json is the default place to store configuration values your application needs at runtime. Think connection strings, API endpoints, feature flags, logging levels, and service settings. The big win is separation of concerns: your code stays focused on behaviour, while configuration stays flexible and changeable across environments.
Under the hood, this is powered by the .NET configuration system and the generic host. .NET builds a configuration pipeline from one or more providers (JSON files, environment variables, command-line args, secret stores, and more). These providers are layered, so later sources can override earlier ones. That layering is what makes appsettings.json a good base configuration, while environment-specific or secret values can override it safely.
What is appsettings.json in .NET
appsettings.json is a JSON file typically located at the root of your project. By convention, it contains key-value pairs and nested sections. .NET loads it on startup and exposes values via IConfiguration. You can read config directly, or bind sections to typed options classes for safer, cleaner code.
Common uses include:
- Application settings like
AppNameorDefaultLanguage - Service endpoints like
BaseUrlorTimeoutSeconds - Logging configuration via the built-in logging providers
- Connection strings (often overridden in production)
- Feature toggles for controlled rollout
How the .NET configuration system works
The core technology is the Microsoft.Extensions.Configuration stack. In practical terms, it gives you:
- Providers that load configuration from various sources (JSON, env vars, Azure Key Vault, etc.)
- Binding that maps configuration sections to strongly typed classes
- Reload support so changes can be detected (mostly useful in development or certain hosting scenarios)
In most modern .NET templates (ASP.NET Core and worker services), the host is created for you and configuration is wired up automatically. The default order is roughly:
appsettings.jsonappsettings.{Environment}.json(for example, Development, Staging, Production)- User secrets (development only, when enabled)
- Environment variables
- Command-line arguments
The key takeaway is last one wins. If the same key exists in multiple sources, the value from the later source overrides earlier ones.
A solid starting appsettings.json
Here is a realistic example with a few common sections:
{
"App": {
"Name": "CloudProinc Portal",
"SupportEmail": "support@example.com"
},
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyDb;Trusted_Connection=True;"
},
"ExternalApis": {
"Payments": {
"BaseUrl": "https://api.payments.local",
"TimeoutSeconds": 15
}
},
"FeatureFlags": {
"EnableNewCheckout": false
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Notice the structure. Group related settings into sections so they stay readable and are easy to bind.
Environment-specific configuration with appsettings.Development.json
Most teams want different settings in development versus production. That is where environment-specific JSON files help.
Create an appsettings.Development.json like this:
{
"ExternalApis": {
"Payments": {
"BaseUrl": "https://sandbox.payments.example"
}
},
"FeatureFlags": {
"EnableNewCheckout": true
},
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}
Only include what changes. You do not need to duplicate every setting. The configuration system merges sections and applies overrides.
How .NET knows the environment
ASP.NET Core uses ASPNETCORE_ENVIRONMENT (and the generic host uses DOTNET_ENVIRONMENT) to decide which environment file to load. Typical values are Development, Staging, and Production.
Reading configuration with IConfiguration
For quick access, you can read values directly from IConfiguration. This is common in small apps or in glue code where binding might feel heavy.
var builder = WebApplication.CreateBuilder(args);
// Read a simple value
var appName = builder.Configuration["App:Name"];
// Read an int with a fallback
var timeout = builder.Configuration.GetValue<int>("ExternalApis:Payments:TimeoutSeconds", 30);
The colon syntax (Section:SubSection:Key) is how nested JSON is addressed.
Preferred approach with strongly typed options
For most production code, binding configuration to a typed class makes your app safer and easier to maintain. You get autocomplete, compile-time checks, and one place to document the meaning of each setting.
Step 1 Create an options class
public class PaymentsApiOptions
{
public const string SectionName = "ExternalApis:Payments";
public string BaseUrl { get; set; } = "";
public int TimeoutSeconds { get; set; } = 15;
}
Step 2 Register it in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<PaymentsApiOptions>()
.Bind(builder.Configuration.GetSection(PaymentsApiOptions.SectionName))
.ValidateDataAnnotations()
.Validate(o => !string.IsNullOrWhiteSpace(o.BaseUrl), "BaseUrl is required");
If you want validation with attributes, add [Required] and other annotations to the properties, then keep ValidateDataAnnotations().
Step 3 Consume via IOptions or IOptionsMonitor
using Microsoft.Extensions.Options;
public class PaymentsClient
{
private readonly PaymentsApiOptions _options;
public PaymentsClient(IOptions<PaymentsApiOptions> options)
{
_options = options.Value;
}
public async Task CallAsync()
{
// Use _options.BaseUrl and _options.TimeoutSeconds
await Task.CompletedTask;
}
}
Use IOptionsMonitor<T> if you want to react to configuration changes at runtime (useful in some hosted scenarios). Most line-of-business apps are fine with IOptions<T>.
Connection strings and the ConnectionStrings section
.NET treats the ConnectionStrings section as a first-class convention. You can access it like any other value, or use helpers:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Default");
In production, prefer overriding connection strings using environment variables or a secret store, rather than committing real credentials into JSON files.
Overriding settings with environment variables
Environment variables are a common way to configure containers, CI/CD pipelines, and cloud services. .NET maps nested keys using double underscores.
For example, to override:
ExternalApis:Payments:BaseUrl
Set an environment variable named:
ExternalApis__Payments__BaseUrl
This plays nicely with Kubernetes, Azure App Service configuration, and most deployment tooling.
Secrets and what not to put in appsettings.json
A simple rule: if leaking a value would hurt you, do not store it in appsettings.json in source control. That includes:
- Production database passwords
- API keys and shared secrets
- Client secrets for OAuth apps
Better options include:
- User Secrets for local development
- Environment variables for deployments
- Managed secret stores like Azure Key Vault or AWS Secrets Manager
The nice part is you do not have to change your code much. You keep reading the same configuration keys, but the values come from a safer provider later in the pipeline.
Practical steps to structure appsettings.json well
- Group by domain. Use sections like
ExternalApis,Storage,Auth,FeatureFlags. - Keep names consistent. Pick a naming style and stick with it.
- Prefer typed options for anything more than a couple of keys.
- Use defaults carefully. Defaults are great for developer experience, but dangerous for security-sensitive settings.
- Document intent. A short comment is not allowed in strict JSON, so consider documenting settings in code (options class) or in a README.
Common pitfalls and how to avoid them
Accidentally relying on the wrong environment
If ASPNETCORE_ENVIRONMENT is not what you think it is, the wrong file loads and values can surprise you. Make environment explicit in your deployment pipeline and verify it in logs at startup.
Storing secrets in JSON
This is the most frequent mistake. If you need local convenience, use user secrets. If you need production safety, use environment variables or a secret manager.
Using IConfiguration everywhere
It works, but it spreads configuration knowledge throughout your code. Typed options centralise it, make it testable, and reduce runtime errors.
Overly flat configuration
A long list of top-level keys gets messy quickly. Nest settings into sections that match how your system is organised.
A simple checklist for your next .NET project
- Create a clean base
appsettings.jsonwith sensible defaults and no secrets. - Add
appsettings.Development.jsonfor local overrides. - Use environment variables or a secret store for production secrets.
- Bind important sections to typed options classes.
- Add validation so the app fails fast when configuration is missing or invalid.
Wrap up
appsettings.json is more than a convenient file in your project. It is the front door to the .NET configuration pipeline, designed to keep your code clean, your deployments flexible, and your environment differences manageable. With a solid structure, typed options, and safe overrides for secrets, you get configuration that supports growth rather than slowing it down.
Discover more from CPI Consulting -Specialist Azure Consultancy
Subscribe to get the latest posts sent to your email.