Starting a new ClojureScript project in 2022. Setup suggestions

OK. Thanks.

I think I must have used that the first time I installed, but at some point afterwards I must have allowed Ubuntu’s package manager to take over.

Though, do you remember how you installed it in the first place? You probably want to follow the same?

Like it’s possible someone packaged Clojure inside of apt.

1 Like

Exactly, there IS a clojure apt package. And that’s still at 1.10.1

I think I did install from Clojure’s own download script once upon a time. But I must have seen that at some point and thought it better to let apt take over.

Obviously that’s part of my problem. And I’ll at least solve that.

Then back to the main issue of creating a project :slight_smile:

Though I think it would be good if ANY of the tools / methods I tried to create new projects with could have just accurately reported that the reason they failed was that my version was outdated. :-/

I’m still very mystified that everyone recommends clj -Tsomething and I was getting

-T is no longer supported, use -A with repl, -M for main, or -X for exec

if the problem was that my stuff was outdated. Has -T been brought back as an option?


Check out these changelogs. (July 28, 2021)

  • Tools - git-based programs that can be installed with a local name. Tools can provide their own usage context in deps.edn.

    • Added new auto-installed tool named tools with functions install, list, remove. See reference.
    • Install a tool with clojure -Ttools install <lib> <coord> :as <toolname>
    • Run a tool with clojure -T<toolname> fn (also takes -X style args)

Clojure CLI

  • New -T option is like -X (executes a function) but does not use the project classpath, instead uses tool classpath (and adds :paths ["."] by default). -T:aliases is otherwise same as -X. -Ttoolname resolves named tool by name and uses that tool lib. (Nov 5, 2021)

Clean up exception handling for -X/-T (Dec 1, 2021)

  • Improved error handling for unknown tool with -T or -X:deps find-versions

So the tools flag was added a lot more recently than I thought. I was assuming it was created sometime before 1.10, then that’s my mistake in initially posting and not having a disclaimer about minimum version requirements. Hope it’s getting on the right track

1 Like

Ya it was brought back, but as a different thing. Like I said, the CLI was quickly iterating for a bit, trying to figure out how to best work, taking feedback from people, and making changes.

Also, the version of Clojure your project or REPL uses, and the version of the Clojure CLI you have don’t have to be the same. You can use the CLI and tools.deps with an older Clojure version to pull down a newer Clojure version to launch your own REPL or APP.

Might be a bit confusing, but it also makes sense in a way.

The Clojure CLI is a bash script with shell commands like -T, -M, -X, -version, and all that.

The shell script delegates the implementation of most commands to a Clojure program which is tools.deps. Tools.deps itself will depend on the “global” version of Clojure installed to run. But what tools.deps does is pull down dependencies to run other Clojure programs, your own basically, so it can pull down a newer version of Clojure to use locally for your project, even if your global version in older.

My point is, don’t think of clj and clojure command as Clojure itself, think of them as the Clojure CLI (command line interface), which is not the actual Clojure compiler and runtime.


And, since I’m here explaining. lein was like a self-contained thing, it did make it a bit easier, but it was not as simple or extendable.

You had all the plugins that would try and monkeypatch things, and it was hard to externally maintain and all that.

Now things are a bit more separated into different area of responsibilities.

  1. The Clojure CLI (command line interface)

First, you got the command line interface, which is clojure. Like I said, that’s a shell script, its all the commands you have to do things like start a REPL, launch an application (within the context of an alias), and all that.

  1. Tools.deps

This manages dependencies and settings for launching Clojure programs with the right set of dependencies and the correct main or init function to call. It’s a Clojure library, you can use it as a library, or you can use it through the Clojure CLI.

This library is configured with the deps.edn file where you can specify paths and dependencies and all that.


This manages build tasks, like generate doc, run tests, create an Uberjar, etc.

This is also a Clojure library, and it is always used as a library. So the way its meant to be used is you create a Clojure program, by convention build.clj at the root, and from that program you require Then you do whatever you want for your build tasks leveraging to make it easier.

Finally, the magic trick that this all creates is that everything just becomes simple Clojure programs with utility libraries. And since the Clojure CLI can be used to launch Clojure programs, that’s all you need to create new “commands” on the CLI itself.

It means you don’t have to change the CLI code to add a new CLI “command”. You can just specify an alias, and use -M, -T, -T:, or -X to call the “command”, which is just a Clojure program exposing either a main method -M, or a callable function -X.

And depending if the program needs to be launched within the context of your project or not, you use -T or -T: instead of -M and -X.

Its a bit less easy at first, but once you get it, it makes a lot more sense, where lein was more magical and obtuse. The new way builds upon simple things that combine.


Well yes.

Thanks, that’s one of the best explanations I’ve seen. And I kind of understand a lot better.

Nevertheless I don’t think it was necessary to expose quite so much breakable dependency management on the users, even if the Clojure team wanted a more flexible system built from components.

I’m sure it would have been possible to give users an experience somewhere between the lein “just works” one and the current degree of confusion without sacrificing this flexibility.

I don’t see why, for example, and tools.deps couldn’t have been written to work with older versions of Clojure itself. They presumably aren’t doing anything very exotic or cutting edge. Just reading EDN and pulling files from remote servers. Does that need to be version 1.11.1.x as opposed to anything after about 1.5? And if they needed a recent version of the CLI itself, why couldn’t they just say “Sorry, this tool needs CLI version X” which would be far easier address?

Anyway, I’ve now upgraded and seem to have successfully installed clj-new, so now onto my next problems.

I do think there can be something between lein and this, but I think the core team expects the community to figure what that is, and build it on top of the core abstractions. There’s this for example: GitHub - babashka/neil: A CLI to add common aliases and features to deps.edn-based projects that does make things feel a bit more “lein” like.

It does work with older versions of Clojure as well. But the commands you were running didn’t work with older versions of the CLI.

What you’re asking for is forward compatibility. You want newer commands to work with older CLIs.

What it gives you is only backwards compatibility, older commands will still work, or throw an appropriate error, when used with newer versions of the CLI.

There’s very little software that ever promises forward compatibility, because how can you make new things work with old things?

You did hit a bit of a confusing edge case I admit, where at the time the error message would have been correct, and what used to be done with -T did need to be done with -M, but since then has also changed to no longer be the case. It was like a good intention of making it clear at the time that turned into a more confusing message later for you. Not sure it was easy to predict.

I will grant you one thing, knowing what version you’re using of the CLI and finding the documentation appropriate for that version is not easy. I’ve been bitten by it as well, where you look at the documentation on (or follow along some blog) and assume you’ve got the same version, but it turns out not.

That said, I expect this to stabilize all quite soon, and these will be forgotten inconveniences.


We had a better on-ramping story in 2012. 10 years later, I would hesitate to bring someone in under the new vision - which is still undergoing breaking changes. Most of these “new” problems were already forgotten (albeit with different classes of gripes) under the legacy tooling.

At least with lein, you had a project.clj and a single point of confusion / source of truth (localized to the project level, with some external complexity if profiles.clj entered into the mix). Now you need to manage deps.edn and its aliases, and its build.clj, and glue them together to approximate what we already had - with the side benefit of having more flexibility and purity going forward. It is reminding me of the configuration morass that cropped up with the cljs tooling. I question if the juice was worth the squeeze at this point, but the herd has moved forward.


We had a better on-ramping story in 2012.


1 Like

Copied from other thread:

I seem to have found a solution that works for me. Using clj-new (which now works thanks to me updating to most recent CLI tools) and the figwheel-main template.

clojure -Tclj-new create :template figwheel-main :name myname/myapp :args '["+deps","–reagent"]'

Then run the app with

clojure -M:fig:build

I hope this helps anyone else with similar confusion to me.

Thanks again everyone.

Yeah, it’s shocking how hard it is to fire up a new clojure project. I’m sure clojure is losing loads of potential users due to how frustrating the tooling is just to get started.

Even now I tend to copy an older project and delete everything that I don’t want in new one.

1 Like

What gets me is not so much that it’s hard.

But that it’s way HARDER than it was 10 years ago.

That should horrify everyone who loves Clojure.

Or rather, it’s probably still OK … in general … to use Lein. But because of so much contradictory documentation out there, it’s more confusing than in 2014 when Lein was your only and default choice. Now more than half of the introductory / beginners / tutorial web pages you’ll find will be giving you contradictory, confusing and possibly non-working advice.

1 Like

Now that you got it working, can you answer this multiple choice survey:

It was harder to get started:

  1. because there are more steps involved, and the tooling is more confusing.
  2. because there wasn’t good up to date tutorials/blog guides explaining how to do so.
  3. because I couldn’t easily find the up-to-date guides, Google search only returned outdated info, and links to guides on pointed to outdated info.
  4. because there are too many “good” ways to do things, and I can’t decide which one I want to go with, and I feel I just want the “Clojure Senior Community Devs” to all agree on and to tell me which way is best to start with.

Pick one or more that applied.


I’d say that 2 / 3 is the biggest issue.

Finding the “right” way to do things was hard because of all the noise from ways that were either a) outdated, b) not right for what I wanted.

Obviously this was compounded by my cli tools being outdated. But it was VERY hard to discover that this was what the problem was. Error messages from failures never mentioned the version of CLI tools (or which tool was specifically the problem)

So to an extent, also 1 for “confusing tooling”, in that the various cli tools are not well explained / demarcated. Your explanation here is the clearest, most informative thing I’ve read about them.

Had I been able to identify the “right” tutorials, the mere fact of more steps probably wouldn’t have been a problem. But yeah, I think the the labeling of options etc. is also confusing in cli.

(BTW: what’s this syntax of -X:blah:blah? etc? It’s neither traditional command line options, nor EDN. It seems like a bit of a kludge which is impenetrable to anyone who is coming cold to it.)

4 is obviously phrased contentiously. But there’s an aspect that, yes, people new to something need some clear guidance without being confronted with all the ambiguity and all the options that experts use.

I’m not saying that the senior devs have to give us the one true way. But someone, somewhere, ought to have an up-to-date “getting started” guide and it would be good if the official Clojure pages (first result of almost all google searches) knew about and could link to it.

To re-emphasize. What’s sad is that I got into and fell in love with Clojure, 8 years ago partly because the onboarding experience was so good then. At that time I had been dabbling with Haskell, and had become frustrated with a couple of things. Somewhere, somehow I read someone saying something nice about Clojure. I remembered all the hype about Lisp goodness too. (I’d played with Lisp but never used it in anger). And on a whim I decided to try what I’d been thinking of doing in Haskell in Clojure (with Quil I think, to do some graphics).

The fact is that within an hour or so I’d installed Clojure, started playing and made enough progress that I was convinced to continue my project with it. Even though it was completely new to me. I was as productive within a couple of days as I am in anything.

I’m very glad I did try it and did have this experience. I love Clojure. I’m convinced it’s the best language I’ve ever used. And it’s my first choice for anything I want to do.

But had, 8 years ago, I hit the kind of stumbling blocks and frustrations I hit in the last week or so, in just trying to boot my project up, I’d have almost certainly given up and either gone back to Haskell or just decided to do it in Python. And I suspect many people today might be having this problem.

I don’t really want to blame or fault senior devs or people behind Clojure for this. Except maybe in that it feels that Leiningen has been a really solid, established and easy to use standard for building and managing Clojure projects. And it seems a bit “high handed” for the Clojure community to decide that it’s deprecated and we should all move onto something else, less beginner friendly. That has created a lot of confusion.

I wish they’d either worked with the Leiningen people to evolve Leiningen and fix what they thought was missing from it, or at least made the UI to CLI building more similar to / compatible with Lein.

Why couldn’t, they, for example, have just used a project.clj file? (Or at least a project.edn file which was closer to the Lein one?) Why not use the same terminology where possible? Leiningen has “lein run”. Why does cli need “cli -X:dev” rather than “cli run dev”? None of these are big issues in their own right, but dozens of them all add up to the sense of confusion and disempowerment if you are coming from lein and expect that you should be able to adapt reasonably quickly. (I don’t expect changing a command-line tool / build-system in a language / ecosystem I already know to have the same learning curve / studying requirements as getting into a whole new language.)

Obviously I accept that “people coming from Lein” and “people new to Clojure” are not the same group.

But where there’s a common theme: Lein successfully encapsulates and hides complexity. Which was great for new users. But also for everybody.

With CLI tools, the Clojure team has decided that hiding complexity is not a priority compared to their other goals. But no-one is telling us that “beginners should use Leiningen, CLI is for advanced users”. The opposite, there’s a strong implication that CLI is the way to go.

But really, if they were going to replace Lein with something new, they should have at least aimed to learn from it, and match it, in terms of complexity hiding.


Feels like the official CLI was written / designed (continues to be designed) by ops minded people. Same uptake in popularity with like-minded folks, with newbs getting caught in the uptake of the new/shiny (as well as the official titling).

I think they changed switches at some point and deprecated a bunch of extant behavior, leading to more confusion on the existing tutorials/blog posts that were out there. My brain just doesn’t really currently comport with their design; e.g. it’s non obvious how the switch vocabulary maps to actions at first glance, so I have to keep looking stuff up. The command oriented nature of lein was easier for me to ingrain and focus on, but I was still able to fan out complicated enough stuff (reference the example project.clj) and use cases through profiles and plugins. There seemed to be no shortage of plugins either.

I think tools.deps/build aim to lower the barrier for you to just do things in clojure, and to eliminate some of the overhead and staticness of project management (no plugin api to mess with). So the prospect is you do everything in clojure, and everything for managing projects is laid bare for you to use behind a “little” API. So complicated builds/testing/deployments can be expressed with the general purpose nature of clojure. This is great from an ops perspective, since you can now shift a lot of (maybe all of) the ops burden into clojure. Given that cognitect was/is supporting (and is now owned by) a large bank doing seemingly everything in clojure, this probably suits their needs (maybe they ditched lein ages ago who knows). Looking at how much it was integrated into the datomic / ions tooling and stuff, it seems like they built the CLI out to support an internal use case first (e.g. it wasn’t some flight of fancy or pure NIH syndrome).

I don’t think it’s on the level of python 2 → 3 fiasco, but there is definitely a lot of ancillary pain, confusion, and chaff for folks caught in the unmanaged migration. There are definitely concrete benefits though; a bit faster time-to-repl since there is no secondary jvm (lein trampoline does this too, but doesn’t work on all systems I have to use); some of the dependency resolution in lein that can lead to odd behavior where legacy deps are preferred seems to be better under tools.deps - so you have to mark exclusions and stuff; you can point to local projects fairly trivially and treat them as deps; same with git repos, where you can just tag or use a commit hash. A lot of this has been ported to lein though with plugins (even delegating to tools.deps), but the concept of shipping mostly .clj files and not dealing with jar infrastructure is first class in the CLI tooling.


When I got started with Clojure in 2010, Leiningen was the only game in town and it was “easy” but not “simple” since it glommed “everything” into one tool. Also, in the early days of ClojureScript, the tooling was pretty awful for actually build real projects: we tried in 2013/2014 at work and decided it wasn’t ready for prime time and built our new apps’ UIs with JS (and still use React.js etc today).

In 2015, we became increasingly frustrated with how limited Leiningen was for our large backend project so we switched to Boot, where it was much easier to extend our build system using Clojure instead of dealing with Leiningen’s painful plugin system and a bunch of shell scripts for stuff that was just too “hard” to do with Leiningen. As part of working with Boot, we moved to a model where our dependencies were stored in external EDN files – long before the core Clojure team created tools.deps.alpha and the CLI.

In 2018, we switched everything from Boot to the new CLI so that we could benefit from a) the core team’s support of the toolchain and b) the cleaner design of the whole deps.edn ecosystem. We’d also begun to run into bugs and performance issues with Boot because our project was so large.

When appeared, we were able to get rid of the remaining ad hoc build scripts etc we had created and move our entire dev/test/CI/build/deploy system to Clojure. Our build.clj file is just over 400 lines and relies on a build subproject in our repo for some things (another few hundred lines).

Both lein and boot allowed you to chain multiple commands together in a single command and that was the biggest loss for us in switching to the CLI early on (we wrote a build shell script that allowed us to specify multiple tasks and it invoked the CLI multiple times under the covers). When appeared, we got that ability back because we could easily write high-level tasks that composed multiple lower-level tasks – “it’s all just functions!”.

We couldn’t do what we do today if we were still using Leiningen – it’s great for small, simple projects and so it’s easy for new developers to pick up but it doesn’t scale to more complex projects, which is where so much of the core team’s design has always shone: at scale.

For pure Clojure projects, the “new” CLI and deps.edn is also pretty good for getting started and the official guides and reference for it cover all the basics. Once you’re at the stage of wanting to build artifacts for distribution or deployment, there’s and again the official guides and reference cover the basics for that too. The main “gap” is deployment to Clojars but GitHub - slipset/deps-deploy: deploy your stuff seems to have become the de facto standard for that (and my wrapper, build-clj, has a built-in deploy task based on that).

For ClojureScript, however, the situation is completely different. The official site has some very bare bones getting started material and makes a lot of assumptions about how you plan to develop/build your project, but as you’ve seen, you really need either figwheel-main (and follow the steps for whatever template you then use) or shadow-cljs. The latter seems to be a more “batteries-included” toolchain and is certainly better documented than the Figwheel stuff but there’s a lot more to getting a ClojureScript project up and running than a Clojure project. At some point, I expect I’m going to have to dive back into the ClojureScript world and figure it out – and I expect I’ll feel like a newbie all over again. Given the advances in the JS/TS ecosystem over the years, I’m almost inclined to try to learn that rather than wrestle with the state of the ClojureScript world – while still very, very happy to have Clojure on the backend!


These kinds of comments make me really angry. This is a completely toxic hot-take, which is based completely on ignorance. I mean yes, go ahead try to actually do a JS/TS project, I’d argue that CLJS is still way simpler but I’m biased.

I mean you just in that very same post described that you have a “build.clj file that is just over 400 lines and relies on a build subproject”. I’d take the challenge and would say that CLJS takes substantially less to set up and build, but (again) I’m biased.

I try to stay silent in these kinds of discussions since I want to avoid the usual figwheel vs. shadow-cljs debates. I do however read them and I’m painfully aware of the docs/guides/templates situation, but again I’d argue this situation isn’t any better on the CLJ side?

Most of the problems and confusion of the OP seem to come from the unfamilirarity with tools.deps. None of that has anything to do with CLJS.

I just want to throw in there that just using lein is fine. There is absolutely no need for deps.edn.


Yeah, I’d have to disagree with @seancorfield here too, though I can understand why he might be daunted at first. But fig/shadow are arguably easier than the CLJ build sitch in some ways - almost like that in-between-lein-and-deps scenario. And leagues simpler than the whole JS ecosystem. The official CLJS docs could use an update/ face-lift maybe.

Right, but I’m talking about how we’ve gone from simple (and easy) with Leiningen and Clojure over a decade ago to a project that outgrew both Leiningen and Boot and is hella complex – and making a point that the tooling scales with complexity but is understandably not always easy for Clojure beginners.

And then I’m drawing a parallel to the fact that I would be starting as a newbie for ClojureScript and likely running into a lot of similar issues to the OP.

It’s a completely pointless (and tautological) argument to make that getting started with ClojureScript is a lot simpler than dealing with a huge Clojure codebase that produces nearly two dozen artifacts from over sixty subprojects.

At work, if I choose ClojureScript, I’m on my own to set everything up and learn how to write idiomatic and performant re-frame apps – but if I go down the JS/TS path I have access to colleagues who know this stuff and have already built out an entire dev/test/CI/build/deploy pipeline and can advise me and help with stuff. Thus, JS/TS is likely to be easier for my situation even if it isn’t going to be simpler.

And @thheller in particular, you know my feelings about the who JS ecosystem and how much I would prefer to avoid it completely in favor of “just” ClojureScript – you’re misreading my intent and getting angry for the wrong reason :slight_smile: