Docs Why Wagoe Libraries Blog GitHub Get Started →
v1.0.0-beta-8 — Now available

Ship your Clojure app.
Not the plumbing around it.

Auth, admin UI, jobs, search, multi-tenancy — 28 things you'd otherwise wire by hand, already there and already following one architecture. You write the part that's actually your product.

$ curl -fsSL https://get.wagoe.org | bash
From nothing to running

Your first module, before your coffee is cold.

This is the whole thing, unedited: new project, one sentence describing a module, schema and tests on screen, migrated, running. If you've ever spent day one of a project on setup, this is day one with Wagoe — every line below is what the CLI printed.

my-app — first run
$ wagoe new my-app
 Project created: my-app/

$ cd my-app
$ bb setup --database sqlite --ai-provider replicate
 Generated resources/conf/dev/config.edn
$ bb quickstart
[4/8] Scaffolding sample module
[8/8] Verifying project structure
━━━ Quickstart Complete ━━━━━━━━━━━━━━━

$ bb scaffold ai "product module with name, price, stock" --yes
 Successfully generated module: product
$ clojure -M:test --focus my_app.product.core.product-test
0 failures.
$ bb scaffold integrate product
$ bb migrate up

$ bb repl
user=> (go)

Recorded in one take against the released v1.0.0-beta-8CI walks the same path on every commit.

28
Libraries you don't have to write
FC/IS
One architecture your whole team follows
1.12
Runs on the Clojure you already use
Zero
Lock-in — plain deps.edn dependencies
What's included

Everything your backend ends up needing anyway.

You know the list: auth, roles, admin screens, background jobs, search, email, file storage, audit logs. Each one is a library here — built the same way, tested the same way, swappable with a config key. Pick the ones you need this sprint; add the rest when you need them.


Shown, not claimed

Declare an entity. Get the screens.

No mockups. Define a user schema and this admin, this form and these validation errors exist — you didn't write any of them. Real output from the current beta.

The generated admin interface listing eight users, with columns for email, name, role, active state and creation date, plus search, filters and a New Users button.
Auto-generated CRUD. Declare an entity; the list, search, filters, sorting and bulk actions come with it.
The generated Create New User form with fields for name, email, password, role and a welcome-email checkbox, and a panel listing the password requirements.
Forms from the same schema. Field types, the role options and the password rules are read from the definition, not written twice.
The same form after an invalid submission, showing Invalid email format under the email field and Password must be at least 8 characters under the password field.
Validation that reaches the screen. The server re-renders the form with the failures against the fields that caused them.
The development dashboard showing 43 components, 223 routes, 8 modules and no recent errors, with a list of the running Integrant components and an environment panel.
A dashboard while you work. Components, routes, schemas, jobs, requests and errors, served on :9999 in development only.

Developer experience

The 2am stack trace, with the fix attached.

Same missing JWT secret. Left: what Clojure tells you. Right: what you actually needed — where it broke in your code, the framework frames folded away, the one line that fixes it, and a (fix!) if you'd rather not type it.

Without Wagoe's devtools
Execution error (ExceptionInfo) at
  shop.checkout.core/issue-session-token (core.clj:6).
JWT_SECRET not configured

Full report at:
/var/folders/cw/hbtpm.../clojure-5181972888.edn
With Wagoe's devtools
━━━ BND-103: Missing JWT Secret ━━━━━━━━━━━━━
JWT_SECRET is not set but the user module is active.

── Your code ────────────────────────────────
  shop.checkout.core/issue-session-token (core.clj:6)
  shop.main/sign-in (main.clj:8)
  shop.main/-main (main.clj:12)

── Framework (12 frames) ────────────────────
  (expand with (explain *e :verbose))

Fix: export JWT_SECRET="your-secret-at-least-32-characters"

Auto-fix: (fix!)  — Generate and set dev JWT_SECRET
Dashboard: http://localhost:9999/dashboard/errors
Docs: bb guide error BND-103
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

REPL

Ask the running system what it is.

Start it with (go), then ask it what's running, which routes exist and what it wants you to do next. No restart, no digging through system maps, no "which component didn't come up this time."

REPL
user=> (status)
┌─ Wagoe Dev ─────────────────────────────────────────┐
│ System:    running (43 components, 0 errors)        │
│ Web:       http://localhost:3000                    │
│ Admin:     http://localhost:3000/web/admin          │
│ nREPL:     port 7888                                │
│                                                     │
│ Modules:   admin, ai, invite, payment, search (+... │
│ Guidance:  full (set :guidance-level :minimal to... │
│                                                     │
│ Try: (status) | (routes) | (modules) | (commands)   │
└─────────────────────────────────────────────────────┘

Architecture

The architecture you agreed on in the kickoff, still there in year three.

Every team decides business logic goes here and I/O goes there. Six months later someone's in a hurry and it doesn't. Wagoe turns that agreement into a directory layout and a lint rule: core/ can't import I/O, and the build fails if it tries. Not a style guide — a check.

Imperative Shell
shell/
I/O · HTTP handlers · Persistence · Side effects
http.clj service.clj persistence.clj
Ports — Protocol Definitions
ports.clj
Interfaces only · Injectable adapters · No implementation
defprotocol
Functional Core
core/
Pure functions · Business logic · No I/O · No mocks needed
validation.clj domain.clj rules.clj

Enforced by directory layout + clj-kondo rules  ·  Every library follows the same structure


28 Libraries

Pick what you need.
Drop what you don't.

Each library is a standard deps.edn dependency. Already have Integrant, Reitit and next.jdbc in place? They stay. Add wagoe-user for auth this week and nothing else — that's a valid way in.


Real code

What you'd actually write.

Four things you'll do in every project — generate a module, model a state machine, go from dev to prod, add auth and audit to a route — as they look in Wagoe.

Terminal
# Natural language module generation
$ bb scaffold ai "order module with customer, total, status"

 Schema defined       schema.clj
 Validation rules     core/validation.clj
 Persistence layer    shell/persistence.clj
 HTTP routes          shell/routes.clj
 Service layer        shell/service.clj
 Tests generated      test/core_test.clj

Generated 6 files in libs/order/
All tests passing: 12/12
schema.clj
(ns wagoe.order.schema
  (:require [malli.core :as m]))

(def Order
  [:map
   [:id          :uuid]
   [:customer-id :uuid]
   [:total       [:and :decimal [:> 0]]]
   [:status      [:enum
                  :pending
                  :paid
                  :shipped
                  :delivered]]
   [:created-at  :instant]])
order/workflow.clj
(defworkflow order-workflow
  {:initial-state :pending
   :states        #{:pending :paid
                    :shipped :delivered
                    :cancelled}
   :transitions
   [{:from :pending
     :to   :paid
     :required-permissions [:finance]}
    {:from  :paid
     :to    :shipped
     :guard :payment-confirmed}
    {:from :shipped
     :to   :delivered}]})

; Pure core logic — no I/O, no mocks needed

Pure business logic

The entire state machine lives in core/ — no I/O, no side effects. Test every transition without a database or mock.

Audit trail by default

Every state change is recorded with who, when, and from/to state. Query with plain SQL.

Guards + permissions

Role-based guards and custom guard functions. The workflow rejects invalid transitions at the transition layer.

config.edn
;;; Development — zero setup, zero config
{:wagoe/db-context
 {:adapter   :sqlite
  :db        "dev.db"}

 :wagoe/cache
 {:adapter   :in-memory}

 :wagoe/observability
 {:log-adapter  :stdout
  :err-adapter  :no-op}}

;;; Production — one key change each. Zero code changes.
{:wagoe/db-context
 {:adapter   :postgresql
  :host      #env DB_HOST
  :pool-size 10}

 :wagoe/cache
 {:adapter   :redis
  :uri       #env REDIS_URL}

 :wagoe/observability
 {:log-adapter  :datadog
  :err-adapter  :sentry
  :dsn          #env SENTRY_DSN}}
routes.clj
;;; Cross-cutting concerns declared alongside the route.

{:path    "/api/admin/orders"
 :methods
 {:post
  {:handler      'handlers/create-order
   :interceptors ['auth/require-admin
                  'audit/log-action
                  'rate-limit/admin
                  'metrics/record-latency]}}}

;;; Service layer — one macro call, all telemetry automatic
(defn create-order [this order-data]
  (service/execute
    :create-order {:order order-data}
    (fn [{:keys [params]}]
      (order-core/prepare
        (:order params)))))

; Automatically instruments: structured logs, metrics,
; Sentry breadcrumbs, PII redaction. No telemetry code needed.

vs the alternatives

If you want to choose every piece, Kit is a fine base.

Kit and Biff are toolkits: small by design, you wire everything. Wagoe makes those choices for you — FC/IS layout, admin UI, multi-tenancy, jobs, search, reports — and asks you to follow its conventions in return. If your team would rather spend that time on the product, this is for you. It's beta-8; Django and Rails have twenty years on it.


Whoever you are on the team, here's your reason.

The same libraries and the same patterns, whether you're two people or two hundred — but what they solve depends on where you sit.


Up in minutes

Five minutes from now.

01
Install the CLI
curl -fsSL https://get.wagoe.org | bash
Requires curl, git, and Babashka (bb). Installs the wagoe command.
02
Scaffold a new project
wagoe new my-app cd my-app
Wires four core modules — core, observability, platform, and user. Add more with wagoe add payments.
03
Start the REPL
source .env clojure -M:repl-clj
HTTP server on :3000, REPL-driven workflow, zero-config SQLite database. wagoe new already generated .env with a real JWT_SECRET.

Try it on something real this week.

Star it, read the code, or ask on Clojurians Slack. It's beta — your first project will find things we haven't, and we'd rather hear it now.

GitHub Clojurians Slack Start Building