Liking cljdoc? Tell your friends :D

admin

Admin UI and CRUD management tooling for Wagoe entities. Generates admin interfaces automatically from entity configuration — no manual forms.

Technology stack

TechnologyPurpose

Hiccup

HTML generation (Clojure data structures → HTML)

HTMX

Dynamic interactions without a frontend build step

daisyUI / Tailwind

Component classes on the Wagoe design tokens

Lucide icons

Icon set via wagoe.shared.ui.core.icons

Key namespaces

NamespacePurpose

wagoe.admin.core.ui

Main admin UI rendering (tables, forms, layout composition)

wagoe.shared.ui.core.icons

Shared Lucide icon rendering

wagoe.admin.shell.http

Admin HTTP handlers and HTMX fragment endpoints

Entity configuration

Admin entity configs are modular — each module owns its config file:

resources/conf/dev/
├── config.edn              ← Main config (uses Aero #include)
└── admin/
    └── users.edn           ← User module's entity config
    └── products.edn        ← Product module's entity config
;; admin/products.edn
{:products
 {:label           "Products"
  :label-singular  "Product"   ; one record, in toasts; default :label made singular
  :list-fields     [:name :sku :price :created-at]
  :search-fields   [:name :sku]
  :hide-fields     #{:deleted-at}
  :readonly-fields #{:id :created-at :updated-at}}}
;; config.edn — include all entity configs
:wagoe/admin
{:enabled?   true
 :entities   #merge [#include "admin/users.edn"
                     #include "admin/products.edn"]
 :pagination {:default-page-size 25}}

Access

Under :wagoe/authorization {:mode :off}, the 1.x default, the admin admits users with role :admin. Under :enforce it reads permissions instead: every entity has admin.<entity>/read and admin.<entity>/write, and the code roles admin.<entity>/viewer and admin.<entity>/editor holding them. Every admin page needs read on one entity in a scope that entity maps, the sidebar lists what the user may read, and actions they may not take are not shown: a viewer opens a record read-only. A platform admin holds everything; a tenant :admin holds what their groups give them. :report keeps the role rule and logs what :enforce would refuse, once a minute per user and permission.

A right held in a scope admits only records inside it. The entity maps each scope key to a field, or to a function from the allowed values to a HoneySQL condition when the scope lies on a related record:

{:orders {:scope {:domain-id :domain-id}}
 :lines  {:scope {:domain-id myapp.admin/line-domain}}}  ; (fn [ids] [:in :order_id {...}])

Lists, counts and search show only rows inside the scope, and a many-to-many shows only the linked records the user may read. A record outside it is a 404; a create or edit that would put one outside it is refused on the field. A relation condition cannot be checked on a record’s new values, so creating and editing there needs the right unscoped. A bare column in the condition is the entity’s own, whatever a query joins. An entity that maps no key admits only unscoped holders.

:require-role is deprecated: nothing reads it, and it goes in 2.0.

Adding a new module

  1. Create resources/conf/dev/admin/{module}.edn

  2. Add #include "admin/{module}.edn" to the #merge vector in config.edn

  3. (reset) to reload configuration

UI conventions

  • Standalone data-entry forms (login, register, create/edit) get form.form-card class

  • Button-wrapper forms (logout, delete, revoke) get no class

  • Page header pattern: h1 + p in a plain [:div], with .page-actions as sibling

  • Tables: [:div.table-wrapper > table.data-table]

Testing

clojure -M:test:test/pg :admin

Can you improve this documentation? These fine people already did:
thijscreemers & Thijs Creemers
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close