Environments
Switch between production, staging, and other environments in Earl templates.
Environments let a single template target different backends — typically a production API and a staging one — without duplicating the command logic. You define named environments at the provider level, then optionally override the full operation for specific environments in each command.
How it works
There are two pieces:
- The
environmentsblock at the top of the template defines the available environments and their values (usually base URLs). This is where you set the default. environment_overridesblocks inside individual commands replace the entire operation when a specific environment is active.
The two pieces are independent. You can use environments without any environment_overrides, and just interpolate {{ vars.base_url }} into your operation URL. Or you can skip the top-level block and use environment_overrides to swap operations entirely.
The environments block
This goes at the top of the template file, before any command blocks:
environments {
default = "production"
secrets = ["github.token"]
production {
base_url = "https://api.github.com"
}
staging {
base_url = "https://staging.github.internal"
}
}When a command runs, the active environment's fields are available as {{ vars.<key> }}. In the example above, {{ vars.base_url }} resolves to https://api.github.com in production and https://staging.github.internal in staging.
secrets lists secrets that apply to all environments. Individual environments can add their own fields — if staging uses a different token, you can put that key name in the staging entry:
environments {
default = "production"
secrets = ["github.token"]
production {
base_url = "https://api.github.com"
}
staging {
base_url = "https://staging.github.internal"
token_key = "github.staging_token"
}
}The top-level secrets list is for secrets shared across all environments. Per-environment values in the environments { } inner block are data fields that end up in vars.*, not automatic secret references.
Using {{ vars.* }} in operations
Once you have an environments block, use vars.<key> anywhere in the operation:
operation {
protocol = "http"
method = "GET"
url = "{{ vars.base_url }}/search/repositories"
auth {
kind = "bearer"
secret = "github.token"
}
}This is the simplest form of environment switching: one operation, parameterized by environment values.
environment_overrides
When the difference between environments is more than a URL — different auth, different protocol, different headers — you can replace the entire operation for a specific environment:
command "search_repos" {
title = "Search repositories"
summary = "Search GitHub repos by query"
description = "Search GitHub repositories."
annotations {
mode = "read"
secrets = ["github.token", "github.staging_token"]
}
param "query" {
type = "string"
required = true
description = "Search query"
}
operation {
protocol = "http"
method = "GET"
url = "{{ vars.base_url }}/search/repositories"
auth {
kind = "bearer"
secret = "github.token"
}
query = { q = "{{ args.query }}" }
}
environment_overrides {
staging {
operation {
protocol = "http"
method = "GET"
url = "https://staging.github.internal/search/repositories"
auth {
kind = "bearer"
secret = "github.staging_token"
}
query = { q = "{{ args.query }}" }
}
}
}
}When the active environment is staging, Earl uses the override operation and ignores the default one entirely. For all other environments, the default operation runs.
The override is a complete replacement, not a merge. If the default operation has headers or query params you want to keep in staging, you need to repeat them.
Activating an environment
Pass --env to earl call:
earl call --env staging github.search_repos --query "rust"Or set a default in ~/.config/earl/config.toml:
[environments]
default = "staging"The --env flag overrides the config file default. The config file default overrides the template's default value.
Protocol switching
By default, Earl rejects environment overrides that switch the protocol (e.g., from http to grpc). This prevents silent behavior changes when swapping environments.
If you need to switch protocols between environments, set the annotation:
annotations {
allow_environment_protocol_switching = true
}Without this, an override that changes protocol will cause Earl to error when the template loads.
Complete example
version = 1
provider = "github"
categories = ["scm"]
environments {
default = "production"
secrets = ["github.token"]
production {
base_url = "https://api.github.com"
}
staging {
base_url = "https://staging.github.internal"
}
}
command "search_repos" {
title = "Search repositories"
summary = "Search GitHub repos by query"
description = "Search GitHub repositories using GitHub's search API."
annotations {
mode = "read"
secrets = ["github.token"]
}
param "query" {
type = "string"
required = true
description = "Search query (e.g. 'language:rust stars:>100')"
}
param "per_page" {
type = "integer"
required = false
default = 20
description = "Results per page (max 100)"
}
operation {
protocol = "http"
method = "GET"
url = "{{ vars.base_url }}/search/repositories"
auth {
kind = "bearer"
secret = "github.token"
}
query = {
q = "{{ args.query }}"
per_page = "{{ args.per_page }}"
}
}
result {
decode = "json"
output = "Found {{ result.total_count }} repos:\n{% for r in result.items[:5] %} - {{ r.full_name }}\n{% endfor %}"
}
}Run against staging:
earl call --env staging github.search_repos --query "language:rust"For a field-by-field reference, see Template Schema. For using external secret URIs in environment-specific auth, see External Secrets.