What Is Ash?
I spent six months with Ash at Alembic. My eureka moment came at the three month mark. I could finally articulate what made Ash Ash.
Why is this post needed? The level is either too low or too high. Even if you never learn, and never use Ash, this post is still worth your time.
I came to Ash because it promised to reduce the boilerplate I had to generate and work with and some of the mundane mostly glue-code I had to write1. Plus, in a world of language models, I was not interested in getting them to generate code I knew could be coded-away. One final reason I joined Alembic was to pursue what I consider the holy grail of code generation, user interfaces, and I did!
So; Let’s get to it. Ash is five things:
- A reflective application kernel
- A core vocabulary around domains, resources, and actions
- A DSL for DSLs
- An installer
- An ecosystem
Let’s work through those.
(1) Ash has run-time and compile-time reflection2. So what? Elixir is already metaprogrammable but it’s different. Elixir is mostly Lisp-like from the box in this respect. Ash is not. It is Smalltalk-like. Instead of wrangling Elixir’s complex AST triples you work with Spark’s simpler ADT and API3. There is less you can do but it is easier to do simple work.
(2) Ash’s core constructs and core vocabulary are (1) an SQL-like DDL for nouns (Ash resources and attributes), and (2) CRUD-like verbs (Ash actions).
(3) Spark is a DSL for describing and working with YAML-like configuration languages in Elixir. A configuration language for configuration languages. It makes it much easier to work on and work with DSLs of a certain shape. Ash’s core constructs and vocabulary is built with Spark. It’s all Elixir, so Elixir constructs are normal in Spark-based DSLs, e.g. Module aliases and function litterals.
(4) Igniter is the installer that has made it outside of the Ash ecosystem. Remember all those installation instructions for Oban? Hooray; No more of that! It can do source code patching and code generation in Elixir and other languages.
(5) Ash is an ecosystem, perhaps, above all. Packages that save time. Take schema migration in the Postgres package: change a resource, reflection powers a diff, the diff powers code generation for a migration. Of course, there is a lot more, from Authentication on the less reflective side to JSON/HTTP APIs on the more reflective side. As of mid 2026, GUIs are not quite there, but there is Ash Admin and Cinder and more in progress.
If you find the above interesting I suggest the learning path that worked for me:
- Official tutorials to equip you with enough vocabulary to ask good questions
- A small project while consulting Devin/DeepWiki
- Read the Ash book to get a more complete picture
- A big project while consulting Devin/DeepWiki
- Read the manual in full so that you know what you’re looking for
Only use Ash when it works for you.
You can always default to plain old Elixir, Ecto, and Phoenix if you need to.
Of course, it’s not always simple, but there are either implicit escape hatches (as above) or explicit escape hatches as in a manual or generic action.
Let’s wrap up. The learning curve is very steep; Take it from me. I’ve worked with a variety of niche and mainstream tech. Ash was by far the hardest to learn4. You will have to invest time to save time and occasionally opt-out (and indeed know when to do so).
I like what Ash does, what it tries to do, but I would have made very different design decisions. That aside, there is an important take-away, that a lean reflective application kernel is easier to write than I thought. That should motivate a high-level reflective API in Elixir and other languages.
-
↩
Even though I am not a fan of macro-heavy systems.
-
↩
Reflection, as opposed to metaprogramming, because the intention is exactly this. Simple functional constructs work better almost all of the time (I stand by this even for Ash).
-
↩
You could argue that the Elixir AST tripple is in fact an ADT.
-
↩
Not the hardest intrinsically but the hardest to learn.