Aero makes Clojure configuration data, with tag literals for the parts that vary
A small library for explicit, intentful configuration.
At a glance
- What is it?
- Aero is a small Clojure library for reading configuration from EDN files, offering tag literals such as #env, #or, #profile, #hostname and #include so that environment differences stay in one file, while the library's design goals insist configuration stays explicit, stored in source control and never becomes a program. It suits teams that want one config file rather than one per environment.
- Who is it for?
- Adopt Aero when a Clojure service is accumulating per environment configuration files and you want a single file whose differences from the default are visible in place, since the tag literals cover environment variables, hostnames, profiles and includes without turning the config into code.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 177 days ago.
- What is it written in?
- Mainly Clojure, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A small library with a strong opinion about config
Aero describes itself as a small library for explicit, intentful configuration, and the README expands that into a set of design goals rather than a feature list. Configuration should be explicit, intentful and obvious, but not clever, and it should be easy to understand what the config is and where it is declared. The justification given is operational rather than aesthetic. Determining config in stressful situations, for example while diagnosing the cause of a production issue, should not be a wild goose chase. The other goals follow from that. Config files are often duplicated on a per environment basis, which attracts all the problems of duplication, but difference between environments has to be allowed for, and the way to answer the reader's question, does this value differ from the default and if so how, is to answer it in place. The library is small by design, and the README makes no claim that it does more than configuration.
The jar trap that decides how you call it
There is one warning in the getting started section that deserves more attention than it usually gets. The README says that even though reading a config by path will work in your REPL and when running tests, it is very likely to catastrophically fail if you run your application from the generated `.jar` file, and that to avoid surprises it is better to always use the classpath resource form, which works in all scenarios. The two forms are:
(require '[aero.core :refer [read-config]])
(read-config "config.edn")and the resource form:
(read-config (clojure.java.io/resource "config.edn"))The reason is that a file path is resolved against the working directory, and a packaged application does not have your source tree as its working directory, while a resource is found on the classpath inside the jar. It is the kind of difference that passes every test and then fails on the first deployment, so the guidance belongs in the first paragraph of your own config module.
The tag literals, and what each one is for
The vocabulary of the library is a set of reader tags, and each solves a specific way configuration tends to diverge. `#env` reads an environment variable, `#envf` splices environment variables into a formatted string, and `#or` provides a list of possibilities with a default at the end. `#join` builds strings, which is useful for connection strings, and `#profile` acts as a reader conditional, taking a map of profile to value and extracting the entry for the profile you name when you read the config, replacing the older `#cond` tag which is now deprecated. `#hostname` switches on the machine, so a port can differ per host, and `#user` does the same for the operating system user. There are also coercion tags: `#long`, `#double`, `#keyword` and `#boolean` parse a string into the corresponding type, and `#merge` merges several maps together. The set is small enough to hold in your head and expressive enough to cover the cases that would otherwise need a per environment file.
Configuration in source control, passwords out of it
Two design goals look contradictory until you read them together. The first is that config should be stored in the source code repository, because when config is left out of source control it festers and diverges from the code base, so the advice is to keep a single config file under version control. The second is that while it is good to keep config in source control, it is important to ensure passwords and other sensitive information remain hidden. The mechanism that satisfies both is `#include`. You split the sensitive parts into local private files that are not committed, and the committed file pulls them in:
{:webserver #include "webserver.edn"
:analytics #include "analytics.edn"}By default `#include` resolves the included file relative to the config file that includes it, which the README notes will not work for jars, and the fix is a custom resolver, either one Aero provides, `resource-resolver` or `root-resolver`, or a map you supply yourself:
(require '[aero.core :refer (read-config resource-resolver)])
(read-config "config.edn" {:resolver resource-resolver})A map resolver is also possible, for example `(read-config "config.edn" {:resolver {"webserver.edn" "resources/webserver/config.edn"}})`.
Why the library argues against environment variables
The stance on environment variables is the most surprising part of the README, and it is stated as a design goal. Aero suggests using environment variables judiciously and sparingly, the way Unix intends, and not going mad, and it links arguments against the practice. The reason given in the tag literal section is more specific. It is considered bad practice to use environment variables for passwords and other confidential information, because it is very easy to leak a process's environment, for example via `ps e -f` or to your application monitoring tool. That is a concrete operational argument rather than a preference, and it explains the library's shape. The environment is a shared, observable namespace, so secrets do not belong there, and `#env` is meant for endpoints and non secret values while `#include` handles the private files.
Data, not code, and the EDN choice underneath
One goal is that config should be data, and the argument is a security one. The README notes that while it can be very flexible to have clever configuration programs, that can be unsafe, can lead to exploits and compromise security, because configuration is a key input to a program, and advises always using data for configuration and avoiding Turing complete languages. The format choice follows from the same thinking. Since most of the technology to read configuration in a safe, secure and extensible way already exists in the Clojure core library as EDN, the library builds on that rather than inventing a parser. A `#ref` tag lets one part of a file refer to another by a vector resolvable with `get-in`, which is the answer to duplication inside a single file, and it sits alongside `#merge` for combining maps. Together with `#include` for splitting files, the library covers both halves of the duplication problem it sets out to solve.
MIT licensed, two build files, and a quiet repository
The licence is MIT, and the distribution is Clojars, with a badge pointing at the project there. The README instructs you to add the dependency to your `project.clj` file, and the repository root carries both a `project.clj` and a `deps.edn`, so it works for Leiningen and tools.deps projects alike, plus a `lumo-test` script and the usual `src/` and `test/` directories. The project is part of JUXT, the company behind XTDB, and support goes through the Clojurians Slack channel that the README links. On maintenance, the repository is not archived, but the last push was on 2026-05-01 and there are no GitHub releases, so the version you get is the version on the default branch and, for a library this small, the risk of a breaking change is lower than the risk of the library being abandoned. If you adopt it, pin the version rather than tracking the branch.
Editorial conclusion
Adopt Aero when a Clojure service is accumulating per environment configuration files and you want a single file whose differences from the default are visible in place, since the tag literals cover environment variables, hostnames, profiles and includes without turning the config into code. Do not adopt it if you need a config format that other languages or tools can read, because the file format is EDN with library specific tags, and the project says as much when it recommends EDN because the reading technology already exists in the Clojure core. Verify first how your application is packaged, because the README warns that reading a config by path works in a REPL and in tests but can fail outright when you run from the generated jar, and use the classpath resource form instead.
Frequently asked questions
How do I read a configuration file with Aero?
Require the namespace with `(require '[aero.core :refer [read-config]])` and call `(read-config "config.edn")`. The README warns that the path form works in a REPL and in tests but can fail when running from the generated jar, so it recommends the classpath form `(read-config (clojure.java.io/resource "config.edn"))`.
Which tag literals does Aero provide?
Among them `#env` and `#envf` for environment variables, `#or` for a list of possibilities with a default, `#join` for building strings, `#profile` for a reader conditional, `#hostname` and `#user` for host and user specific values, `#long`, `#double`, `#keyword` and `#boolean` for coercion, `#include` for other config files, `#merge` for combining maps and `#ref` for referring to other parts of the file.
How does Aero let me keep secrets out of source control?
By using `#include` to pull in local private files that are not committed, while the rest of the configuration stays in a single file in the repository. For jars, you need a custom resolver such as `resource-resolver` or `root-resolver`, or a map of file to path.
Is Aero still maintained?
The repository is not archived and the last push was on 2026-05-01, but the project has no GitHub releases, so you depend on the default branch. It is MIT licensed and published on Clojars, and support is via the Clojurians Slack channel.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/juxt-aero)Was this useful?