skip to content

In Karate, in what order are `karate-base.js`, `karate-config.js` and `karate-config-<env>.js` evaluated, and what happens when two of them return the same key?

level: middleimportance: must knowfreq 62%

answer

  1. Three files, one fixed order
  2. The environment-specific one runs last
  3. Later file wins on a shared key
  4. Top-level replace, not a deep merge
  5. All three share one JavaScript context

basics

~10 s

Karate evaluates karate-base.js first, then karate-config.js, then karate-config-<env>.js, applying each returned map in turn. A later file wins on a shared key, and it replaces that whole top-level value rather than merging into it.

solid answer

~50 s

The chain is fixed: `karate-base.js`, then `karate-config.js`, then `karate-config-<env>.js` — and only the middle one is really expected to exist. Each file is evaluated and each returned map is applied key by key onto the same scenario variable space, so a key returned by a later file **overwrites** the earlier one. The overwrite is at the **top level only**: if `karate-config.js` returns `{ urls: { api: 'a', web: 'b' } }` and the env file returns `{ urls: { api: 'd' } }`, the scenario sees `urls` with only `api` — `web` is gone, because the object was replaced, not deep-merged. All three run on the **same JavaScript context**, which is why a helper function returned by `karate-base.js` is already a callable name inside `karate-config.js`. The env file is looked for only when `karate.env` is set.

code

javascript · 4 lines
javascript
// karate-base.js  -- evaluated first
function fn() {
  return { hostFor: function (env) { return env ? env + '.example.com' : 'localhost'; } };
}

go deeper

for a junior

Memorise the order and the direction of the override: base, then the main config, then the environment-specific one, and the last one to set a key wins.

for a middle

Explain why the override is a top-level replace and not a deep merge, and why a helper defined in the base file is callable by name inside the config file.

for a senior

Anticipate the failure mode. A nested block overridden by an env file loses its untouched siblings, which reads as a missing variable far away from the config file that caused it.

for a principal

Decide how much of the environment difference belongs in a per-environment file at all, given that each file you add is another place a reviewer must look to answer what a suite is actually pointed at.

## The three files and their fixed order Karate evaluates up to three JavaScript config sources before a top-level scenario, always in this order: 1. **`karate-base.js`** — optional, and rare. It exists for teams shipping a reusable framework layer in a JAR that wants to hand downstream projects a set of defaults and helpers. 2. **`karate-config.js`** — the one every project has. This is where the variables common to all environments live. 3. **`karate-config-<env>.js`** — optional, and looked for **only when `karate.env` holds a value**. With `karate.env` set to `qa`, Karate looks for `karate-config-qa.js`. The order is not configurable: no priority setting, no list to reorder, no fourth file. | File | Position | Required | Typical job | |---|---|---|---| | `karate-base.js` | first | no | shared helpers a framework layer ships to its consumers | | `karate-config.js` | second | expected in every project | the variables common to every environment | | `karate-config-<env>.js` | third, only when `karate.env` is set | no | the per-environment differences | ## One JavaScript context, three evaluations All three files are evaluated in the **same** JavaScript context for that scenario, one after the other, and each returned map is applied as variables before the next file runs. That single detail explains the whole design: - A function returned by `karate-base.js` is already a **bare callable name** by the time `karate-config.js` is evaluated. A base file returning `{ urlFor: function (env) { ... } }` lets the config file write `baseUrl: urlFor(karate.env)` with no import and no `read()`. - The same is true one step further down: `karate-config-<env>.js` sees everything the two files before it applied. - It also means a name declared in two files collides in the obvious way — the later declaration wins. ## The merge is a top-level replace This is the part that bites. Each returned map is applied **key by key at the top level**. It is not a recursive merge. ```javascript // karate-config.js function fn() { return { urls: { api: 'http://api.dev', web: 'http://web.dev' }, retries: 3 }; } // karate-config-qa.js function fn() { return { urls: { api: 'http://api.qa' } }; } ``` Run with `karate.env` set to `qa`, a scenario sees `retries` as `3` — the env file said nothing about it, so the earlier value stands — but `urls` is now `{ api: 'http://api.qa' }` and **`urls.web` no longer exists**. The whole `urls` object was replaced. The practical rules that follow: - Keep values that differ per environment as **flat top-level keys**, not as branches of one nested object. - If you do keep a nested block, an env file that touches it must return the block **whole**. - A key an env file never mentions is simply left alone — partial override at the top level works exactly as expected. ## What is optional, and what a failure looks like `karate-base.js` and `karate-config-<env>.js` are both optional and their absence is unremarkable — most projects have neither. `karate-config.js` is expected, but its absence is still not an error: the run continues with no config variables at all. A file that **throws**, however, is different. The chain stops at the file that threw, the remaining files are not evaluated, and the scenario fails — the failure attaches to the scenario's first step, naming the config file that blew up. That is a good thing: it means a broken env file cannot half-apply and leave the suite pointed at a mixture of two environments. ## Reading the chain in a real project When a variable is not what you expected, walk the chain in order and ask three questions: 1. Which of the three files sets that key? 2. Is `karate.env` actually set, so the third file was even looked for? 3. Is the key nested inside an object that a later file replaced wholesale? Most "my override did not apply" reports are the second question, and most "my override applied too much" reports are the third. ## When a config file throws Absence and failure are handled very differently. `karate-base.js` and `karate-config-<env>.js` are both optional, and `karate-config.js` — while expected in every project — is not fatal by its absence either: the run simply continues with no config variables set. A file that **throws** stops the chain. The files after it are not evaluated, and the scenario fails with the error attached to its **first step**, naming the config file that blew up. That is the right behaviour, and it is worth being able to explain why: a half-applied chain would leave a scenario holding some keys from the common config and none of the environment overrides, which is a suite silently pointed at a mixture of two environments. Failing the scenario outright is strictly safer than running it against an incoherent configuration. ## Telling which files actually loaded The commonest support question on this mechanism is not "what is the order" but "did my file even load". Two habits settle it quickly: - Log the file's own identity from inside each config file — a single `karate.log('config:', ...)` line per file — so the run output states which of the three actually ran. - Return a marker key from each file, a distinct `configSource` value say, and print it in a smoke scenario. A common-file marker where you expected the env file's tells you two things at once: the env file did not load, and `karate.env` is probably unset.

  • How would you override just one field of a nested config object from a Karate env-specific config file?
    You cannot, with the chain alone — the later map replaces the whole top-level key. Either flatten the value so the differing field is its own top-level key, or have the env file rebuild the block completely. Some teams keep a helper in `karate-base.js` that takes the base block and returns a merged copy, since the env file can call it.
  • Why is `karate-base.js` evaluated before `karate-config.js` rather than after?
    Because its purpose is to *supply* things the project's own config builds on. It is aimed at a framework layer shipped to other teams: defaults and helper functions that a downstream `karate-config.js` can call by name and freely override. Running it last would invert that — the shared layer would stamp over each project's own choices.
  • What happens to the rest of the chain when one config file throws an exception?
    It stops there. The files after the failing one are not evaluated, and the scenario fails with the error attached to its first step, naming the config file. Nothing half-applies, so you never end up with a scenario holding a partial mixture of two environments' settings.

saying these in an interview costs you the question

  • Says the environment-specific file is evaluated first
  • Claims the three maps are deep-merged recursively
  • Thinks the order can be configured or reprioritised
  • Believes karate-base.js is required in every project
  • Assumes the env file loads even when karate.env is unset