SkillAgentSearch skills...

Malli

High-performance data-driven data specification library for Clojure/Script.

Install / Use

/learn @metosin/Malli

README

malli

Build Status cljdoc badge Clojars Project Slack bb compatible

Data-driven Schemas for Clojure/Script and babashka.

Metosin Open Source Status: Active. Stability: well matured alpha.

<img src="docs/img/malli.png" width=130 align="right"/>

Presentations:

Try the online demo, see also some 3rd Party Libraries.

Want to contribute? See the Development guide.

<img src="docs/img/malli-defn.png" width="600" />

Hi! We are Metosin, a consulting company. These libraries have evolved out of the work we do for our clients. We maintain & develop this project, for you, for free. Issues and pull requests welcome! However, if you want more help using the libraries, or want us to build something as cool for you, consider our commercial support.

Motivation

We are building dynamic multi-tenant systems where data models should be first-class: they should drive the runtime value transformations, forms and processes. We should be able to edit the models at runtime, persist them and load them back from a database and over the wire, for both Clojure and ClojureScript. Think of JSON Schema, but for Clojure/Script.

Hasn't the problem been solved (many times) already?

There is Schema, which is an awesome, proven and collaborative open-source project, and we absolutely love it. We still use it in many of our projects. The sad part: serializing & de-serializing schemas is non-trivial and there is no proper support on branching.

Spec is the de facto data specification library for Clojure. It has many great ideas, but it is opinionated with macros, global registry, and it doesn't have any support for runtime transformations. Spec-tools was created to "fix" some of the things, but after five years of developing it, it's still a kind of hack and not fun to maintain.

So, we decided to spin out our own library, which would do all the things we feel is important for dynamic system development. It's based on the best parts of the existing libraries and several project-specific tools we have done over the years.

If you have expectations (of others) that aren't being met, those expectations are your own responsibility. You are responsible for your own needs. If you want things, make them.

The library

Clojars Project

Malli requires Clojure 1.11 or ClojureScript 1.11.51.

Malli is tested with the LTS releases Java 8, 11, 17, 21 and 25.

Quickstart

(require '[malli.core :as m])

(def UserId :string)

(def Address
  [:map
   [:street :string]
   [:country [:enum "FI" "UA"]]])

(def User
  [:map
   [:id #'UserId]
   [:address #'Address]
   [:friends [:set {:gen/max 2} [:ref #'User]]]])

(require '[malli.generator :as mg])

(mg/generate User)
;{:id "AC",
; :address {:street "mf", :country "UA"},
; :friends #{{:id "1dm",
;             :address {:street "8", :country "UA"},
;             :friends #{}}}}

(m/validate User *1)
; => true

Syntax

Malli supports Vector, Map and Lite syntaxes.

Vector syntax

The default syntax uses vectors, inspired by hiccup:

<!-- :test-doc-blocks/skip -->
type
[type & children]
[type properties & children]

Examples:

;; just a type (String)
:string

;; type with properties
[:string {:min 1, :max 10}]

;; type with properties and children
[:tuple {:title "location"} :double :double]

;; a function schema of :int -> :int
[:=> [:cat :int] :int]
[:-> :int :int]

Usage:

(require '[malli.core :as m])

(def non-empty-string
  (m/schema [:string {:min 1}]))

(m/schema? non-empty-string)
;; => true

(m/validate non-empty-string "")
;; => false

(m/validate non-empty-string "kikka")
;; => true

(m/form non-empty-string)
;; => [:string {:min 1}]

Map syntax

Alternative map-syntax, similar to cljfx:

NOTE: For now, Map syntax in considered as internal, so don't use it as a database persistency model.

;; just a type (String)
{:type :string}

;; type with properties
{:type :string
 :properties {:min 1, :max 10}}

;; type with properties and children
{:type :tuple
 :properties {:title "location"}
 :children [{:type :double}
            {:type :double}]}

;; a function schema of :int -> :int
{:type :=>
 :input {:type :cat, :children [{:type :int}]}
 :output :int}
{:type :->
 :children [{:type :int} {:type :int}]}

Usage:

(def non-empty-string
  (m/from-ast {:type :string
               :properties {:min 1}}))

(m/schema? non-empty-string)
;; => true

(m/validate non-empty-string "")
;; => false

(m/validate non-empty-string "kikka")
;; => true

(m/ast non-empty-string)
;; => {:type :string
;;     :properties {:min 1}}

Map-syntax is also called the Schema AST.

Why multiple syntaxes?

Malli started with just the Vector syntax. It's really powerful and relatively easy to read, but not optimal for all use cases.

We introduced Map Syntax as we found out that the overhead of parsing large amount of vector-syntaxes can be a deal-breaker when running on slow single-threaded environments like Javascript on mobile phones. Map-syntax allows lazy and parseless Schema Creation.

We added Lite Syntax for simplified schema creation for special cases, like to be used with reitit coercion and for easy migration from data-specs.

Example Address schema

Following example schema is assumed in many of the following examples.

(def Address
  [:map
   [:id :string]
   [:tags [:set :keyword]]
   [:address
    [:map
     [:street :string]
     [:city :string]
     [:zip :int]
     [:lonlat [:tuple :double :double]]]]])

Validation

Validating values against a schema:

;; with schema instances
(m/validate (m/schema :int) 1)
;; => true

;; with vector syntax
(m/validate :int 1)
;; => true

(m/validate :int "1")
;; => false

(m/validate [:= 1] 1)
;; => true

(m/validate [:enum 1 2] 1)
;; => true

(m/validate [:and :int [:> 6]] 7)
;; => true

(m/validate [:qualified-keyword {:namespace :aaa}] :aaa/bbb)
;; => true

;; optimized (pure) validation function for 
View on GitHub
GitHub Stars1.7k
CategoryProduct
Updated3d ago
Forks231

Languages

Clojure

Security Score

100/100

Audited on Mar 24, 2026

No findings