# Clojure Malli

> Data validation with Malli schemas in Clojure. Use when working with: (0) malli, (1) data validation or coercion, (2) defining schemas for maps, collections, or domain models, (3) API request/response validation, (4) form validation, (5) runtime type checking, or when the user mentions malli, schemas, validation, data contracts, or data integrity.

- Skill: `majiayu000/clojure-malli` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/clojure-malli`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/clojure-malli/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/clojure-malli

---


# Malli Data Validation

Malli validates data against schemas. Schemas are Clojure data structures.

## Quick Validation

```clojure
(require '[malli.core :as m])
(require '[malli.error :as me])

;; Validate
(m/validate [:map [:name :string] [:age :int]]
            {:name "Alice" :age 30})
;; => true

;; Get errors
(-> [:map [:name :string] [:age :int]]
    (m/explain {:name "Alice" :age "thirty"})
    (me/humanize))
;; => {:age ["should be an integer"]}
```

## Quick Coercion

```clojure
(require '[malli.transform :as mt])

;; Decode string input to proper types
(m/decode [:map [:port :int] [:active :boolean]]
          {:port "8080" :active "true"}
          (mt/string-transformer))
;; => {:port 8080, :active true}

;; Coerce = decode + validate (throws on error)
(m/coerce [:map [:id :int]] {:id "42"} (mt/string-transformer))
;; => {:id 42}
```

## Common Schema Patterns

### Maps with Required/Optional Keys

```clojure
[:map
 [:id :uuid]                              ;; required
 [:name :string]                          ;; required
 [:email {:optional true} :string]        ;; optional
 [:role {:default "user"} :string]]       ;; optional with default
```

### Constrained Values

```clojure
[:string {:min 1 :max 100}]    ;; string length 1-100
[:int {:min 0 :max 150}]       ;; integer range
[:enum "draft" "published"]    ;; one of these values
[:re #".+@.+\..+"]             ;; regex match
```

### Collections

```clojure
[:vector :int]                 ;; vector of ints
[:set :keyword]                ;; set of keywords
[:map-of :keyword :string]     ;; map with keyword keys, string values
[:tuple :double :double]       ;; fixed [x, y] pair
```

### Unions and Conditionals

```clojure
;; Simple union
[:or :string :int]

;; Nilable
[:maybe :string]               ;; string or nil

;; Tagged union with dispatch
[:multi {:dispatch :type}
 [:user [:map [:type [:= :user]] [:name :string]]]
 [:admin [:map [:type [:= :admin]] [:role :string]]]]
```

### Nested Structures

```clojure
[:map
 [:user [:map
         [:name :string]
         [:address [:map
                    [:city :string]
                    [:zip :string]]]]]]
```

## Performance: Cache Validators

```clojure
;; BAD - creates validator every call
(defn process [data]
  (when (m/validate schema data) ...))

;; GOOD - cached validator
(def valid? (m/validator schema))
(defn process [data]
  (when (valid? data) ...))

;; Same for decoders
(def decode-request (m/decoder schema (mt/string-transformer)))
(def coerce-request (m/coercer schema (mt/string-transformer)))
```

## Key Gotchas

1. **decode doesn't validate** - returns invalid data as-is. Use `coerce` for safety.
2. **Maps are open by default** - extra keys allowed. Use `{:closed true}` to reject them.
3. **Keys are required by default** - use `{:optional true}` for optional keys.

## Detailed References

- **[Schema Syntax](references/schema-syntax.md)** - All builtin types, properties, operators
- **[API Reference](references/api-reference.md)** - Core functions with signatures
- **[Registries](references/registries.md)** - Reusable schema patterns

