Configuration language
The CASC configuration format
CASC is a configuration language with a real specification: nested blocks, typed values from durations to IP ranges, variables, environment reads, references to other keys, imports and loops — in a syntax that stays readable as plain text.
A first look
A CASC file is a list of statements that together build one tree of named values. This one reads an environment file, a few environment variables and a shared variable, and refers from one key to two others:
#@version = 1.0
import "env/${APP_ENV:dev}.casc"
@region = ${REGION:"eu-west"}
server {
host = "0.0.0.0"
port = !int(${PORT:8080})
}
database {
host = "db.@{region}.internal"
*password = ${DB_PASSWORD:?"DB_PASSWORD is required"}
timeout = 500ms
cache = 512MiB
}
health.url = "http://%{server.host}:%{server.port}/health"#@version = 1.0— every file starts with its format version.importpulls in another file; its path may read the environment.@regionis a variable,${…}reads the environment,%{…}refers to another key of the finished tree.500msand512MiBare values of their own types, not strings to parse later.*passwordis marked secret: the value stays redacted wherever it ends up.:?"…"makes a missing environment variable a load error with your own message.
Design
- One kind of map. A block and a dotted path are two spellings of the same tree, and blocks written twice merge.
- Typed literals. Durations, byte sizes, dates and times, IP addresses and tuples are values in the language.
- Three references, one grammar.
@{}variables,${}environment and%{}config paths share the same defaults, guards, indexes and filters. - Explicit merging. Later writes win and blocks deep-merge; the sigils
~,+and-replace, append and remove when that is what you mean. - Nothing unknown passes silently. A tag, resolver or import scheme nobody registered is a load error that names it.
- Secrets in the syntax. A
*on a key marks its value secret, and interpolating it keeps the result redacted.
The name is a pun: a cooper makes barrels, and a casc is a cask. The format is what the barrel holds.
File facts
| Property | Value |
|---|---|
| Extension | .casc |
| Encoding | UTF-8 |
| First line | #@version = 1.0 — mandatory, in every file |
| Comments | # followed by a space, to the end of the line |
| Result | One tree: blocks are maps, leaves are typed values |
| Grammar | PEG, written in Aether — see Grammar |
Implementations and tools
These pages describe the format. Each of the following has its own documentation for how it loads or exposes it.
| Name | Language | Role |
|---|---|---|
| cooper | Elixir | The reference implementation |
| cooper_config | Elixir | Uses a CASC file as the configuration of a Mix project or release |
| CASC for VS Code | TypeScript | Highlighting, snippets and a language server |
| cooper | Praxis | A port of Cooper, generated from the same grammar |
| cooper_prx | Praxis | Reads a Praxis project’s config.casc |
| ichor | Elixir | The grammar compiler both parsers are generated with |
On these pages
- File structure — header, comments, keys, blocks, merging.
- Values — every literal type.
- References — variables, environment, config paths, filters, tags.
- Imports and loops — splitting and generating configuration, with a complete example.
- Grammar — the formal grammar and where the reference implementation still differs from it.