When appsettings.Development.json settings do not override appsettings.json, configuration sources or setup order usually cause the mismatch.
You expect development settings to win. You tweak a connection string or feature flag in appsettings.Development.json,
run the site, and the app still reads the base value. That pattern can waste a lot of build and debug time, because it feels random
and the log output does not always point at the root cause. Once you understand how the configuration stack works in ASP.NET Core,
the problem turns into a checklist instead of a guessing game.
This guide walks through the typical reasons for appsettings.development.json not overriding base values. You will see where
ASP.NET Core loads each source, which ones win, and how to spot conflicts from env variables, user secrets, Docker
images, or cloud hosts. Along the way you get small code snippets and project file tweaks you can drop straight into a real app.
What Appsettings.Development.Json Does In Asp.Net Core
ASP.NET Core reads configuration from several layers. The base file appsettings.json comes first. Then the runtime
loads an extra JSON file that matches the current ASPNETCORE_ENVIRONMENT value, such as appsettings.Development.json
or appsettings.Production.json. Later sources, such as user secrets, env variables, and command line
arguments, can replace any value that came from JSON files.
With the default host builder, JSON configuration loads in this typical order, where each later step overrides the previous one
when it uses the same setting path.
- Base JSON — Values from
appsettings.jsonfor all slots. - Slot JSON — Values from
appsettings.Development.jsonor another slot specific file. - User secrets — Local secrets from Secret Manager during development.
- Env vars — Values from process or host env variables.
- Command line — Values passed through
dotnet runor service arguments.
If a development JSON override surprises you, that usually means a later source wins, or the slot JSON
never loads in the first place. Before diving into rare bugs, confirm that the app runs under the expected slot and that the
configuration stack uses the default order.
Many teams keep only safe defaults in the base file and treat slot specific JSON as the place for real connection strings,
test URLs, and feature flags. That pattern works well as long as everyone understands that JSON is just one layer. When a value
still refuses to change, think about which other layer might be involved, then move step by step through the stack instead of
changing random fields and hoping the right one sticks.
Appsettings.Development.Json Not Overriding In Dev Slot
The most common group of issues comes from slot name mismatches. The code searches for a file based on the ASPNETCORE_ENVIRONMENT
or DOTNET_ENVIRONMENT value. When that value does not match the file name, the JSON provider never loads the file, so nothing can
override the base values.
Quick Slot Checks
- Log the slot — Write
builder.Environment.EnvironmentNameto the log on startup. - Match the name — Keep the file name in sync with the slot, such as
Development,Staging, orProduction. - Check case on Linux — On Linux containers the file system is case sensitive, so
developmentandDevelopmentdiffer. - Verify both variables — When you use generic host, either ASPNETCORE_ENVIRONMENT or DOTNET_ENVIRONMENT can drive the slot.
A common trap in Docker is setting ASPNETCORE_ENVIRONMENT=development in the container while the file on disk is
named appsettings.Development.json. On a case sensitive file system, the runtime looks for
appsettings.development.json and never finds the file. The fix is to pick one casing and reuse it for the slot name,
env variable value, and JSON file name.
When you still see dev JSON overrides fail, even when the slot name and file name match, turn on
logging for configuration and restart the app. The provider output shows which files load and which settings arrive from each
layer, so you can see whether the slot JSON appears at all.
Fix Configuration Builder Order And Host Setup
In minimal hosting the generated Program.cs file already wires up JSON files and later sources in the right order. Custom code
sometimes changes that order. If the builder adds the slot JSON file before the base file, the base file wins. If you forget to
add the slot JSON file at all, only the base file and later sources remain.
A safe setup in Program.cs for a typical web app looks similar to this snippet.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true)
.AddUserSecrets(optional: true)
.AddEnvironmentVariables();
var app = builder.Build();
The call chain sets the content root path first so that JSON files load from the correct folder, then adds the base file, then
adds the slot specific file, then user secrets, then env variables. Each step can replace existing values that use the
same setting path. That means a connection string from the slot JSON can still be replaced by a connection string from an
env variable, even when the override in appsettings.Development.json looks correct at first glance.
If you use the older generic host setup with Host.CreateDefaultBuilder, that helper already calls
AddJsonFile for the base file, the slot file, user secrets, and env variables in the right order. Problems
usually appear only when extra configuration calls run before or after that helper or when a custom configuration builder
replaces the default one.
When you override the default builder, try to keep all configuration code in one place near the start of Program.cs. That makes
it easier to scan the order and spot mistakes such as calling AddJsonFile twice with different options or adding
custom providers ahead of JSON files. A short experiment where you comment out pieces of the chain one by one can reveal which
step brings the unexpected value back into the app.
File Name, Location, And Build Output Problems
Even with the right slot and builder order, the runtime cannot load what it cannot see. JSON files must live under the content
root and must be copied to the output folder. A missing setting in the project file or a stray folder path in the solution can
leave your development JSON stuck in the source tree instead of the build output where the app runs.
Common File And Build Issues
| Issue | Symptom | What To Check |
|---|---|---|
| File outside content root | Overrides only work in Visual Studio or not at all | Keep JSON files next to Program.cs or the web project file |
| CopyToOutput missing | Values differ between local build and published build | Set CopyToOutputDirectory to PreserveNewest in the project file |
| Wrong publish profile | Dev slot works, cloud deploy ignores overrides | Open the publish profile and confirm JSON files go to the right folder |
In many teams one person adds appsettings.Development.json, runs the app in Debug, and everything looks fine. Another
person uses a different publish profile or runs the app from the command line and sees base values only. That pattern nearly
always points back to file copying or content root setup rather than a bug in configuration binding.
Complex Sections, Arrays, And Partial Overrides
JSON configuration maps into hierarchical sections. Simple scalar values such as strings or numbers override cleanly from one
file to the next. Complex objects and arrays behave differently. When you override a nested object in a slot file, you may need
to repeat the whole section, not only the one field you plan to change, or the base section stays in place for any missing
values.
Think about a section like this in appsettings.json.
"ThirdPartyApi": {
"BaseUrl": "https://api.example.com",
"TimeoutSeconds": 30,
"UseSandbox": false
}
When you add a matching section in appsettings.Development.json and include only a new BaseUrl, the
other fields come from the base file. That works well in many cases. In some older ASP.NET Core versions and some edge cases
with arrays, deep nested objects require the full section in the slot file. If you see override behaviour that looks random for
one section, try copying the entire object into the slot JSON and then change only the values you need for local debugging.
Arrays use zero based indexes as part of the setting path. A base file might define three entries and the slot file might define
only one entry with index zero. The final configuration keeps the slot value for index zero and keeps the base values for the
other indexes. When you think a dev JSON override fails for an array, check the raw configuration settings through a
diagnostic endpoint or through a quick logging loop that prints all settings under that section.
When Env Variables Or Secrets Win Instead
Env variables and user secrets sit above JSON files in the configuration stack. That means you can write a clean
override in appsettings.Development.json and still see a different value at runtime because a variable or secret
takes the last word. This tends to happen on build agents, in Docker files, and on cloud platforms that set defaults behind the
scenes.
Places Where Higher Layers Override Dev Json
- Build scripts — CI or release pipelines that inject connection strings or URLs.
- Docker compose files —
environment:sections that inject settings for containers. - Cloud app settings — Portal or CLI settings that map to configuration settings.
- User secrets — Local secret values that stick around after debugging sessions.
To prove which source provides the final value, log the configuration provider list on startup. The built in configuration types
expose their names, so you can see JSON, user secrets, env variables, command line, and any custom provider you add. Once
you see that a host level setting beats your JSON file, you can decide which one should win and adjust the chain.
For sensitive data such as passwords and API settings, keep values out of JSON on disk. Use Secret Manager on local machines and a
cloud secret store or host specific service in higher slots. Then keep only non secret defaults in JSON files and use slot
specific files mainly for safe toggles and defaults that help developers run the app quickly.
Many platforms also inject their own settings at deploy time. App service platforms, container orchestrators, and job runners
often include default connection strings or logging flags. Reading the provider list and printing a few sample values during
startup gives you a map of those extras, so you do not waste time blaming JSON files when a host level default actually controls
the final result.
When you treat the full stack of configuration sources as a layered system, appsettings.development.json not overriding turns
into a traceable symptom instead of a mystery. Each layer has a clear place and clear rules about which value wins last, so the
fix usually comes down to a tweak in one file, one build script, or one env variable during local debugging sessions.
