A cross-reference database for Clojure code, built on clj-kondo static analysis (sponsor Borkdude!). Analyze your project once, then ask questions like "who calls this function?", "what does this function depend on?", and "where is this protocol implemented?"
clj-xref builds a searchable cross-reference database from your Clojure source code. It works in two phases:
Think of it as ctags or cscope for Clojure, but with semantic understanding of vars, namespaces, protocols, and multimethods. The API is inspired by SBCL's sb-introspect (who-calls, who-references, who-macroexpands) and Smalltalk's senders/implementors queries.
Feeding context to LLMs. Instead of guessing or having it search your entire src/ tree, it can query the xref database for the dependency neighborhood of the function you're working on — just the relevant code, in far fewer tokens.
Understanding unfamiliar code. You inherit a large Clojure codebase. You need to know what calls process-payment before you change its signature. who-calls gives you the answer instantly, across every namespace.
Dead code detection. Find vars that are defined but never referenced from anywhere. Stop carrying code that nothing uses.
Codebase visualization. Export the call graph or namespace dependency graph for documentation, onboarding, or architecture reviews.
CI and automation. Run lein xref or clj -T:xref generate in CI to produce a cross-reference artifact. Use it downstream for impact analysis, change-risk estimation, or custom linting rules.
REPL-driven exploration. Load the database in your REPL and explore interactively. Since queries return plain Clojure maps and vectors, you can compose them freely with filter, map, group-by, or anything else.
Add clj-xref to your :plugins vector in project.clj:
:plugins [[com.github.danlentz/clj-xref "0.1.1"]]
To also use the query API from your REPL, add it to :dependencies:
:dependencies [[com.github.danlentz/clj-xref "0.1.1"]]
Add the :xref alias to your deps.edn (project-local or ~/.clojure/deps.edn):
{:aliases
{:xref {:extra-deps {com.github.danlentz/clj-xref {:mvn/version "0.1.1"}}
:main-opts ["-m" "clj-xref.cli"]
:ns-default clj-xref.tool}}}
The same alias serves both modes:
clj -M:xref <command> — CLI subcommands (see Command-line interface below).clj -T:xref generate ... — tool invocation for database generation (see Generating the database).To use the query API from your own code, add clj-xref as a dependency:
{:deps {com.github.danlentz/clj-xref {:mvn/version "0.1.1"}}}
The fastest way to use clj-xref. With the :xref alias from Installation in place:
clj -M:xref init # generate the database
clj -M:xref who-calls myapp.orders/process-payment
clj -M:xref calls-who myapp.web/handler
clj -M:xref who-implements myapp.protocols/Billable
clj -M:xref unused # find dead code
clj -M:xref ns-deps myapp.orders
clj -M:xref ns-dependents myapp.orders
clj -M:xref apropos process
clj -M:xref graph myapp.core/main
The database is auto-generated on first query if .clj-xref/xref.edn doesn't exist.
clj-xref ships two drop-in artifacts for Claude Code:
1. /xref slash command — doc/claude-slash-command.md. Copy into .claude/commands/xref.md in your project to expose the queries as user-initiated slash commands:
/xref init — generate/regenerate the xref database
/xref who-calls ns/fn — who calls this function?
/xref calls-who ns/fn — what does this function call?
/xref who-implements ns/Protocol — who implements this protocol?
/xref unused — find dead code (defined but never referenced)
/xref ns-deps ns — what namespaces does this one depend on?
/xref ns-dependents ns — what namespaces depend on this one?
/xref apropos pattern — search for vars matching a name pattern
/xref graph ns/fn — show transitive call graph
2. CLAUDE.md project guidance — doc/claude-md-template.md. Paste the snippet into your project's CLAUDE.md to teach Claude to invoke xref proactively during normal coding work — before changing a signature, before deleting a var, when tracing flow in unfamiliar code. Unlike the slash command (user-initiated), the CLAUDE.md guidance shifts the initiative to Claude based on context.
Both artifacts wrap the same CLI and work with any AI assistant that can run shell commands. The CLI is the interface; the slash command and CLAUDE.md snippet are convenience layers for different invocation styles.
You can also generate the database explicitly:
# Leiningen
lein xref
# deps.edn tool
clj -T:xref generate
clj -T:xref generate :paths '["src"]' :output '"target/xref.edn"'
Incremental mode re-analyzes specific files and merges into the existing database:
lein xref :only src/myapp/orders.clj
clj -T:xref generate :only '["src/myapp/orders.clj"]'
Note: incremental mode only re-analyzes the specified files. If you change an exported definition (e.g., rename a function or change it to a macro), callers in other files retain stale metadata until a full rebuild.
(require '[clj-xref.core :as xref])
;; Load the generated database
(def db (xref/load-db))
;; Who calls this function?
(xref/who-calls db 'myapp.orders/process-payment)
;; => [{:kind :call, :from myapp.web/checkout-handler,
;; :to myapp.orders/process-payment,
;; :file "src/myapp/web.clj", :line 47, :col 5, :arity 2}
;; {:kind :call, :from myapp.batch/retry-failed,
;; :to myapp.orders/process-payment,
;; :file "src/myapp/batch.clj", :line 23, :col 7, :arity 2}]
;; What does this function call?
(xref/calls-who db 'myapp.web/checkout-handler)
;; Who implements this protocol?
(xref/who-implements db 'myapp.protocols/Billable)
;; What dispatch values exist for this multimethod?
(xref/who-dispatches db 'myapp.events/handle-event)
;; Which namespaces depend on this one?
(xref/ns-dependents db 'myapp.orders)
;; Find dead code
(xref/unused-vars db)
;; Transitive call graph (depth 3, outgoing)
(xref/call-graph db 'myapp.orders/process-payment {:depth 3 :direction :outgoing})
;; => #{[myapp.orders/process-payment myapp.db/transact]
;; [myapp.db/transact myapp.db/get-conn] ...}
;; Search for vars by name
(xref/apropos db #"process")
Since results are plain maps and vectors, compose with standard Clojure:
;; All external callers of process-payment (excluding same-namespace calls)
(->> (xref/who-calls db 'myapp.orders/process-payment)
(remove #(= "myapp.orders" (namespace (:from %)))))
;; Vars in myapp.util that nothing in the project references
(xref/unused-vars db)
If you don't need the EDN file, you can analyze and query in-memory directly:
(def db (xref/analyze ["src" "test"]))
(xref/who-calls db 'myapp.orders/process-payment)
This requires clj-kondo on the classpath.
An example /xref slash command for Claude Code is included in doc/claude-slash-command.md. Copy it to .claude/commands/xref.md in your project to add /xref who-calls, /xref unused, etc. as Claude Code commands.
All query functions take a database (from load-db or analyze) and return vectors of maps.
| Function | Returns |
|---|---|
(who-calls db sym) | Call sites of sym |
(calls-who db sym) | Vars called by sym |
(who-references db sym) | All references to sym (calls, reads, macroexpansions) |
(who-macroexpands db sym) | Sites where the macro sym is expanded |
(who-implements db sym) | Implementations of the protocol sym |
(who-dispatches db sym) | defmethod dispatch values for the multimethod sym |
(ns-vars db ns-sym) | All var definitions in a namespace |
(ns-deps db ns-sym) | Namespaces that ns-sym depends on |
(ns-dependents db ns-sym) | Namespaces that depend on ns-sym |
(unused-vars db) | Vars defined but never referenced |
(call-graph db sym opts) | Transitive call graph as #{[from to] ...} edges |
(apropos db pattern) | Vars matching a name pattern (string or regex) |
Each entry in a query result is a map:
{:kind :call ; :call, :reference, :macroexpand, :dispatch, :implement
:from 'myapp.web/handler ; the var containing this reference
:to 'myapp.db/query ; the var being referenced
:file "src/myapp/web.clj"
:line 42
:col 5
:arity 2} ; for :call kind — which arity was used
The clj-xref.graph namespace generates DOT format for visualization:
(require '[clj-xref.graph :as graph])
;; Namespace dependency graph
(spit "ns-deps.dot" (graph/ns-dep-dot db))
;; Call graph from a specific function
(spit "calls.dot" (graph/call-graph-dot db 'myapp.web/handler {:depth 2}))
Then render with dot -Tpng ns-deps.dot -o ns-deps.png.
Here is clj-xref's own namespace dependency graph, generated by running the tool on itself:

And the internal call graph (project-internal edges only):

clj-xref uses clj-kondo as its analysis engine. clj-kondo statically parses your Clojure source (without evaluating it) and produces detailed analysis data: every var definition, every var usage, every protocol implementation.
clj-xref transforms this raw analysis into a normalized data model and writes it as EDN. At query time, it reads the EDN and builds in-memory indexes (group-by :to, group-by :from, group-by :file) for O(1) lookups.
Source files --> clj-kondo --> clj-xref.analyze --> .clj-xref/xref.edn
|
clj-xref.core
|
who-calls, ns-deps, ...
:macroexpand entries), but cannot see what the macro expands into. If a macro generates calls to other functions, those calls are invisible unless clj-kondo has a hook for that macro.(map f coll) — clj-xref knows that map is called, but cannot resolve what f is. Higher-order function patterns create edges that no static analysis can fully capture.The generated file is plain, human-readable EDN:
{
:version 1
:generated "2026-04-13T20:23:57Z"
:project "my-app"
:paths ["src" "test"]
:namespaces [
{:name my.app.core, :file "src/my/app/core.clj", :line 1, :col 1}
]
:vars [
{:name my.app.core/main, :ns my.app.core, :local-name main, ...}
]
:refs [
{:kind :call, :from my.app.core/main, :to my.app.db/connect, :file "src/my/app/core.clj", :line 12, :col 5}
]
}
One entry per line within vectors, so the file is greppable and diffable.
clj-xref includes a benchmark that measures the token reduction from xref-guided context selection. It asks Claude the same questions about the codebase under two strategies — whole source tree vs xref-guided neighborhood — and compares input token counts and answer quality.
# Requires ANTHROPIC_API_KEY and clj-http (included in :dev profile)
lein measure-improvement
lein measure-improvement :model claude-sonnet-4-6
On clj-xref's own source, the xref-guided approach selects 1-3 files instead of 8, reducing context by 66-80%.
clj-xref is built entirely on the analysis engine of clj-kondo by Michiel Borkent (borkdude). clj-kondo's fast, accurate static analysis of Clojure code — without requiring evaluation — is what makes clj-xref possible. If you find clj-xref useful, consider sponsoring clj-kondo.
:analysis output that clj-xref consumes.who-calls, who-references, who-macroexpands API that inspired clj-xref's query interface.Copyright 2026
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |