Emacs Lisp
Last reviewed
May 2, 2026
Sources
No citations yet
Review status
Needs citations
Revision
v1 · 4,083 words
Improve this article
Add missing citations, update stale details, or suggest a clearer explanation.
Last reviewed
May 2, 2026
Sources
No citations yet
Review status
Needs citations
Revision
v1 · 4,083 words
Add missing citations, update stale details, or suggest a clearer explanation.
Emacs Lisp (often shortened to Elisp) is the dialect of Lisp used as the extension language for GNU Emacs. It first appeared in 1985 with GNU Emacs version 13 and was designed by Richard Stallman as a successor to the MacLisp-based extension language used in earlier Emacs implementations on Multics and the Lisp Machine. Almost the entire user-facing portion of Emacs, every editing command, every major mode, every package on the MELPA archive, is written in Emacs Lisp; the editor's C core mostly handles redisplay, low-level I/O, the byte-code interpreter, and the garbage collector.
Elisp is a Lisp-2 with separate function and variable namespaces. It started life with dynamic scoping inherited from MacLisp, gained optional lexical scoping in Emacs 24.1 (June 2012), and now defaults to lexical scoping for new files. The language is interpreted, byte-compiled to .elc files, or natively compiled to shared libraries (.eln) via libgccjit since Emacs 28.1 (April 2022). For most Emacs users, Elisp is the only programming language they ever write to customize a piece of software they actually use; for many in the older MIT AI Lab tradition, it was also their first encounter with the broader Lisp family.
In 2024 and 2025 Elisp picked up another new role. A wave of packages, gptel, ellama, copilot.el, aidermacs, and several Model Context Protocol clients and servers, turned Emacs into a fairly serious workbench for talking to large language models. Running on the same buffer-and-mode primitives that power Org mode and Magit, these tools tend to feel less like add-ons and more like the editor adapting to a new genre of file.
Emacs as a name predates GNU Emacs by nearly a decade. The first Emacs was written in 1976 at the MIT AI Lab by Stallman, Guy Steele, and Dave Moon as a set of macros ("Editor MACroS") for the TECO editor running on ITS. TECO Emacs was extensible, but its extension language was TECO itself, a notoriously cryptic command language that Stallman later described as a write-only medium. The TECO version of Emacs would not strictly speaking be a Lisp at all.
Lisp entered the picture through two parallel projects in the late 1970s. At Honeywell's Cambridge Information Systems Lab, Bernard Greenberg wrote Multics Emacs in MacLisp around 1978; the editor itself was written in Lisp, and so were user extensions. Roughly contemporaneously, Daniel Weinreb and Mike McMahon wrote EINE ("EINE Is Not Emacs") and its successor ZWEI ("ZWEI Was EINE Initially") for the MIT Lisp Machines, also in Lisp. Greenberg's experiment in particular convinced Stallman that an extensible editor written in Lisp was both feasible and substantially more pleasant for users than the TECO model.
When Stallman began work on the GNU project in 1984, a new Emacs was one of his first big targets. He briefly forked James Gosling's C-based Gosling Emacs (which used a small Lisp-flavored interpreted language called Mocklisp), but quickly replaced Mocklisp with a real Lisp dialect derived from MacLisp. GNU Emacs 13, the first public release of GNU Emacs, was put out on March 20, 1985. It already shipped with the Lisp interpreter, byte-code compiler, and a fairly complete set of editing commands written in Elisp. Version 15.34, released later in 1985, became the first version distributed widely outside MIT.
A practical constraint shaped the early language. In 1985 a typical workstation might have one megabyte of RAM and no virtual memory, so the implementation had to be small. Emacs's bytecode is roughly as old as Emacs itself, and the basic shape of the byte-code machine has barely changed since. The hybrid design (a small C core, a byte-code interpreter, and a large Elisp library on top) was a memory budget decision before it was a design philosophy.
Elisp sits between MacLisp and modern Common Lisp in style. The cleanest way to summarize its core properties:
| Property | Detail |
|---|---|
| Family | Lisp, descended from MacLisp |
| Namespaces | Lisp-2 (functions and variables in separate cells) |
| Typing | Dynamic, strong |
| Scoping | Dynamic by default historically; lexical optional from 24.1, default for new files in current Emacs |
| Evaluation | Eager, applicative-order |
| Compilation | Interpreter, byte-compiler (.elc), native compiler via libgccjit (.eln) |
| Concurrency | Single-threaded by default; cooperative threads via make-thread; subprocesses via make-process |
| Numerics | Arbitrary-precision integers since 27.1; floats; no full numeric tower |
| Garbage collection | Mark-and-sweep, runs in the C core |
| License | GPLv3 (same as GNU Emacs) |
Being a Lisp-2 means a symbol like list can name both a function and a variable without collision, and you reach the function via (list 1 2 3) or (symbol-function 'list) and the variable through symbol-value. This is the same arrangement as Common Lisp and the original reason funcall and function exist; Scheme and Clojure by contrast unify the two.
Dynamic scoping was the original behavior. In a let form, a binding established the variable's value globally for the duration of the form's execution, including inside functions called from the body. This made the entire Emacs configuration model possible (let-bind case-fold-search, call any search function, restore on exit) but also caused subtle bugs when a function shadowed a variable a caller relied on. Stefan Monnier's lexical-binding work in Emacs 24.1 added a per-file opt-in via a file-local variable on the first line:
;;; my-package.el --- A description -*- lexical-binding: t; -*-
When this header is set, let and lambda create lexical closures, and the byte compiler can do better static analysis. Emacs 27.1 made lexical-binding the default for interactive evaluation, and Emacs 28 converted all the bundled Elisp files to use lexical scoping. Dynamic binding is still supported and still useful: variables declared with defvar (or defcustom) are dynamically scoped even in lexical files, which is exactly what you want for user-configurable knobs.
Elisp is homoiconic like every other Lisp: programs are S-expressions, S-expressions are lists, and lists can be manipulated as data. This means macros are first-class, the reader and printer round-trip, and the compiler can be written largely in Elisp itself. The native compiler, in fact, is mostly Elisp code, with a small C back end gluing it to libgccjit.
Most of Elisp's surface syntax mirrors Common Lisp, with smaller defaults and a few buffer-oriented additions. The headline forms a working Elisp programmer uses every day:
(defun greet (name)
"Return a greeting for NAME."
(format "Hello, %s!" name))
(defvar my/counter 0
"How many times we've greeted someone.")
(defcustom my/default-name "world"
"Default name passed to `greet'."
:type 'string
:group 'my)
(defmacro my/with-counter (&rest body)
`(progn (setq my/counter (1+ my/counter)) ,@body))
A few features really do feel specific to Elisp rather than imported wholesale from a generic Lisp:
Buffers, markers, overlays. A buffer is the central editing object. Markers track positions that move with insertions and deletions; overlays attach properties (faces, keymaps, display strings) to ranges. These are first-class objects manipulated from Lisp, which is why a package like Magit can paint a complex Git status view without ever rendering its own widget toolkit.
Hooks. A hook is a variable holding a list of functions called at a defined moment, before-save-hook, after-change-functions, prog-mode-hook. add-hook and remove-hook are the polite way to extend behavior; users add functions, packages add functions, the system calls them in order. Hooks are how almost every customization slot in Emacs is exposed.
Advice. Advice lets you wrap an existing function with code that runs before, after, or around the original, without editing the original's source. Older code uses defadvice, but Stefan Monnier's nadvice library, available since 24.4, exposes the cleaner advice-add and advice-remove API:
(advice-add 'find-file :before
(lambda (&rest _args) (message "Opening a file")))
Advice is powerful in the same way and for the same reasons it is dangerous: it lets a third-party package change Emacs's behavior in a way the original author never anticipated. Emacs's own developers have a complicated relationship with advice and prefer hooks, but in practice every nontrivial Emacs configuration uses some.
Interactive commands. A function becomes a user-callable command by adding (interactive ...) as its first body form. The interactive specification describes how to gather arguments (read a buffer name, read a region, prompt with completion). A command can be called from Lisp like any function or invoked from the keyboard via a keymap binding.
Regular expressions. Elisp regexes use a slightly idiosyncratic backslash-heavy syntax inherited from older GNU regex but support the usual character classes, groups, alternations, and a handful of buffer-aware extensions like \< (word start) and \= (point).
Customize. defcustom declares a variable along with its type, group, and (optionally) a setter. The Customize UI, accessed via M-x customize, generates an interactive editor for these variables that writes back to the user's init file. It is a surprisingly early example of schema-driven configuration.
The original Elisp standard library was tiny by design. List operations, basic string handling, regex, file and buffer primitives, and the editor commands themselves. Anything more sophisticated lived in optional libraries. Two libraries in particular shape modern Elisp:
cl-lib is the closest Elisp gets to Common Lisp. It provides namespaced versions of most useful Common Lisp constructs (cl-loop, cl-defun, cl-defstruct, cl-case, generic functions via cl-defmethod) under a cl- prefix. Older code used the unprefixed cl library, but that polluted the global namespace and is now deprecated. Notably cl-lib does not implement full CLOS, only a generic-function subset; it has no full numeric tower; and its condition system is much simpler than Common Lisp's.
seq and map provide generic, polymorphic operations over sequences and key-value collections. They use the same cl-defmethod dispatch as cl-lib. Together with cl-lib, they bring Elisp programming much closer to a modern Lisp idiom than the bare core does.
A few third-party libraries became so widespread that they are de facto standard. dash (list operations with a Clojure-flavored API), s (string utilities), f (file path manipulation), and transient (the keyboard-driven menu system Magit pioneered) are dependencies of half the popular packages on MELPA.
For most of Emacs's history there was no built-in package manager. Users downloaded .el files, dropped them in a directory, edited their .emacs to (load ...) them, and hoped nothing conflicted. Tom Tromey's ELPA (Emacs Lisp Package Archive) and its associated package.el library, started in 2007, formalized this. ELPA was integrated into Emacs core in version 24.1 (2012), which is also the release that introduced lexical-binding.
Four archives matter today:
| Archive | URL | Curation | Notes |
|---|---|---|---|
| GNU ELPA | elpa.gnu.org | FSF copyright assignment required | Official, bundled with Emacs |
| NonGNU ELPA | elpa.nongnu.org | No copyright assignment | Bundled with Emacs since 28.1 |
| MELPA | melpa.org | Recipe-based, builds from upstream Git | Largest archive, both stable and unstable channels |
| MELPA Stable | stable.melpa.org | Tagged releases only | The conservative MELPA channel |
MELPA, originally Milkypostman's ELPA, was started by Donald Curtis around 2012 as a recipe-driven build system that pulls from upstream repositories. It is by a wide margin the largest Elisp package archive and is what most modern configurations rely on.
On top of package.el, two configuration helpers dominate:
use-package, by John Wiegley, is a declarative macro for organizing package configuration. It bundles loading, autoloading, key binding, hook setup, and configuration into a single readable form. It became part of Emacs core in version 29.1.straight.el, by Radian software's Radon Rosborough, takes a different approach: rather than wrapping package.el, it clones each package's Git repository directly and lets the user pin specific commits. It is popular with users who want reproducible configurations.The Elisp package ecosystem is wide and mostly maintained by hobbyists, which means it is spotty, charming, and occasionally astonishing. A handful of packages have broken out of the editor-extension genre into something more like applications.
| Package | Author | What it is |
|---|---|---|
| Org mode | Carsten Dominik (now maintained by Ihor Radchenko) | Outline-based note taking, todo/agenda, literate programming, document export. Created in 2003. |
| Magit | Marius Vollmer (2008), now Jonas Bernoulli | Git porcelain. Most-downloaded non-library package on MELPA. |
| Dired | Built-in | Directory editor where the directory listing itself is an editable buffer. |
| ERC, Rcirc | Built-in | IRC clients. |
| Gnus | Lars Magne Ingebrigtsen | Mail and news reader. |
| Helm, Ivy, Vertico | Various | Completion frameworks. |
| Company, Corfu | Various | In-buffer completion at point. |
| LSP-mode, Eglot | Vibhav Pant; João Távora (Eglot) | Language Server Protocol clients. Eglot joined Emacs core in 29.1. |
| Tree-sitter | Yuan Tao (tree-sitter-langs); native in 29.1 | Incremental parsing for syntax highlighting and structural navigation. |
| AUCTeX | Various | LaTeX editing. |
| SLIME, Sly | Helmut Eller; Stelian Iancu (Sly) | Common Lisp IDE. |
| Geiser | Jose Antonio Ortega Ruiz | Scheme IDE. |
| CIDER | Bozhidar Batsov | Clojure IDE. |
| Tramp | Michael Albinus | Transparent remote file editing over SSH, Docker, sudo. |
Org mode in particular outgrew its origin. It started as Carsten Dominik's outliner for organizing his life, but it now ships in core, has its own export back ends for HTML, LaTeX, and more, supports literate programming through Babel (executable code blocks in any of dozens of languages), and acts as the substrate for the TaskJuggler integration, agenda, and clocking systems. Several long-form blogs and academic papers are written entirely in Org.
Elisp goes through three layers of execution. They coexist in the same image, and a single function may live in any one or in two simultaneously.
| Layer | Files | Notes |
|---|---|---|
| Interpreter | .el | Tree-walking interpreter in C. Used during development and for code that has not been compiled. |
| Byte compiler | .elc | Compiles Lisp to a stack-based bytecode. Loader prefers .elc over .el if both exist. The byte VM is roughly the same shape as it was in 1985. |
| Native compiler | .eln | Andrea Corallo's libgccjit-based ahead-of-time compiler. Translates Elisp via byte-code IR through GCC to a .so per file, loaded at startup or on demand. Typical speedups range from 2x to 5x. Merged in 28.1, April 2022. |
The native compiler is interesting partly because most of it is written in Elisp. The pipeline is: Lisp source goes through the byte compiler to get a bytecode listing; an Elisp pass lowers the bytecode to a small SSA-like IR, runs optimization passes (constant propagation, type inference, unused argument removal), and then a small C backend builds the libgccjit IR and emits a shared library. This means Andrea Corallo's compiler can be debugged, instrumented, and extended from inside Emacs itself.
Native compilation is asynchronous by default: when a .elc is loaded, Emacs queues the corresponding .eln for background compilation, swaps it in when ready, and falls back to the bytecode in the meantime. For end users the only visible sign is a faster Emacs that stays warm and a ~/.emacs.d/eln-cache/ directory full of compiled artifacts.
The garbage collector is a stop-the-world mark-and-sweep run inside the C core. Emacs 28 and 29 made several improvements to GC pause times and to memory layout; ongoing experiments target more incremental and concurrent collection, but as of Emacs 30 the basic strategy is unchanged.
Emacs always had a thriving subculture of "talk to a remote service from inside the editor" packages, dating back to the IRC and Gnus era. The arrival of large language models in 2023 pushed that subculture into a much busier phase. Because Elisp gives you cheap and immediate access to buffers, regions, regex, and processes, building a competent LLM client is roughly a weekend project, and the resulting field is correspondingly cluttered.
A representative slice of the active packages as of 2025-2026:
| Package | Author | Focus |
|---|---|---|
gptel | Karthik Chikmagalur | General LLM client. Talks to OpenAI, Anthropic (Claude), Gemini, Ollama, llama.cpp, OpenRouter, Groq, DeepSeek, and any OpenAI-compatible endpoint. Supports tool calling and Org mode branching conversations. |
ellama | Sergey Kostyaev | LLM tasks (translation, code review, summarization) on top of the llm library, with Ollama as default. |
chatgpt-shell | Alvaro Ramirez | comint-style shell for chat models. |
copilot.el | Community (copilot-emacs org) | GitHub Copilot completion client over Microsoft's copilot-language-server. |
copilot-chat.el | Cyril Pierre de Geyer | Copilot's chat interface in Emacs. |
codeium.el | Codeium | Codeium / Windsurf completion. |
tabnine | Tabnine | Tabnine completion. |
aidermacs | Matthew Zeng (M.Z.M.D.) | Wrapper around Paul Gauthier's Aider that runs in an Emacs comint buffer with native ediff for reviewing diffs. |
aider.el | Tninja | Lighter-weight Aider UI. |
claude-code.el | Various | Front-end to the Claude Code CLI. |
mcp.el / mcp-server-lib.el | Lizqwerscott; Laurynas Biveinis | Model Context Protocol client and server libraries, exposing Emacs functionality (buffers, files, elisp eval) to LLMs. |
llm | Andrew Hyatt | Provider-agnostic library used by ellama and others. Backend abstraction over Ollama, Vertex, OpenAI, and friends. |
gptel is probably the package that most resembles where the genre is settling. It does not enforce a chat-window paradigm; you can call it from any buffer, on any region, and stream the response into the current buffer, into a side window, or back as a comment. Tool calling is wired through plain Elisp functions, which means an LLM tool ends up looking and feeling like an interactive command.
The MCP packages are a more recent development. Anthropic's Model Context Protocol gives a model a way to call out to local tools, files, and APIs, and Emacs is a natural fit on both sides of the protocol. As an MCP client, an Emacs LLM frontend can use file servers, web fetchers, and shell tools the same way Claude Desktop or Claude Code does. As an MCP server, Emacs can hand a model live access to its buffers, its regex search, its eval, and its rich introspection (describe-function, describe-variable, the byte compiler's warnings).
At the configuration-framework level, both Doom Emacs (Henrik Lissner) and Spacemacs (Sylvain Benner) have shipped LLM-aware modules and layers; on top of either, getting from a bare install to a working Claude or GPT client is normally one or two use-package blocks.
The LLM ecosystem is also messy. Hobbyist packages with two contributors live next to corporate-maintained ones, APIs change weekly, and a particular package's claim to support model X may mean anything from "production-ready" to "works on my laptop." Anyone evaluating these tools today should expect to swap them out at least once.
Elisp is sometimes a beginner's first Lisp, especially for users who came to programming through customizing their editor. That tends to leave a slightly distorted view of the family. The basic comparison:
| Feature | Elisp | Common Lisp | Scheme | Clojure |
|---|---|---|---|---|
| Namespaces | Lisp-2 | Lisp-2 | Lisp-1 | Lisp-1 |
| Default scoping | Dynamic historically; lexical now | Lexical (with dynamic via special) | Lexical | Lexical |
| Object system | cl-defstruct, cl-defmethod (subset of CLOS) | Full CLOS with MOP | None standard; libraries available | Protocols, records, multimethods |
| Condition system | Simple condition-case / signal | Full restartable conditions | Variable per implementation | Java exceptions |
| Numeric tower | Integers (bignum since 27.1), floats | Full tower including ratios and complex | Full tower in standard Scheme | Java numerics + BigInt/Ratio |
| Concurrency | Single-threaded; cooperative threads | Implementation-defined | Implementation-defined | JVM threads, STM, core.async |
| Tail calls | Not optimized | Implementation-defined | Required | Recur form |
| Macros | Defmacro | Defmacro + reader macros | Hygienic syntax-rules / syntax-case | Defmacro + syntax-quote |
| Compilation | Bytecode and native | Implementation-specific (typically native) | Implementation-specific | JVM bytecode (and ClojureScript to JS) |
| Primary use case | GNU Emacs extension | General-purpose | Teaching and research; small embedded | JVM application development |
A Common Lisp programmer dropping into Elisp will mostly miss CLOS, the condition system, and numeric tower; almost everything else has a recognizable counterpart, often via cl-lib. A Scheme programmer will be surprised by Lisp-2 namespaces, quote rather than ', the lack of guaranteed tail-call optimization, and how imperatively most Elisp code is written. A Clojure programmer will find Elisp's data structures (mostly lists, vectors, hash tables) limited and its mutability everywhere a culture shock.
What Elisp has that none of the others do is a giant, opinionated, weird application built in it that you can crack open and modify while it runs. That is the entire pitch.
In practice, Elisp lives almost entirely inside GNU Emacs and its forks. The major footprints:
| Setting | Usage |
|---|---|
| GNU Emacs | The host. Native compilation, byte compiler, and library all live here. |
| Spacemacs / Doom Emacs / Prelude | Configuration distributions layered on Emacs. Their layer / module systems are themselves Elisp. |
| XEmacs | The historical fork, now mostly dormant; a slightly different Elisp dialect with overlapping but not identical APIs. |
| Aquamacs | macOS-flavored Emacs distribution. |
| Remacs | An aborted attempt at a Rust port of Emacs that kept Elisp at the surface. |
emacs-ng | An active project bundling Deno-based JavaScript alongside Elisp; Elisp is still primary. |
Elisp does not really have a life outside Emacs. There is no significant Elisp deployment target on a server, no Elisp web framework, no Elisp-on-the-JVM. The handful of standalone Elisp interpreters that exist (mostly research projects) are curiosities. The language and the application are tightly coupled, which is a feature and a limitation depending on your view.
| Person | Contribution |
|---|---|
| Richard Stallman | Designed Elisp; founded GNU Emacs and was its longtime principal maintainer. |
| Stefan Monnier | Long-time co-maintainer; author of nadvice (advice-add), the lexical-binding work for 24.1, and many compiler improvements. |
| Eli Zaretskii | Lead maintainer (with John Wiegley) since 2016; central figure in MS-DOS / Windows ports and bidirectional text. |
| John Wiegley | Maintainer 2015 onward; author of use-package, Eshell, and ledger-mode. |
| Andrea Corallo | Author of the libgccjit-based native compiler; co-maintainer since 2023-2024. |
| Lars Ingebrigtsen | Author of Gnus; maintainer through Emacs 28 and 29. |
| Stefan Kangas | Co-maintainer since 2023. |
| Carsten Dominik | Created Org mode (2003). |
| Jonas Bernoulli | Magit maintainer (since 2013); author of the transient library. |
| Karthik Chikmagalur | Author of gptel. |