Eglot vs lsp-mode in 2026: Which Language Server Client Should You Use?

Emacs has two mature LSP clients: Eglot, which ships with Emacs 29+ as a built-in package, and lsp-mode, a third-party package with a far larger feature surface. Choosing between them is not just a matter of features — it is a decision about how much configuration complexity you want to carry, what your workflow actually demands, and whether you are optimising for responsiveness or for capability.

This article compares both in detail, with specific guidance for PHP and Drupal development where both are viable choices.


The Core Philosophy Difference

Eglot's design goal, stated explicitly in its README, is to be a "minimal, opinionated" LSP client that leverages existing Emacs infrastructure rather than replacing it. Diagnostics go to Flymake. Completions go to completion-at-point. References appear in standard *xref* buffers. The entire package is around 4,000 lines of Elisp.

lsp-mode takes the opposite stance: it provides its own UI components (lsp-ui), its own debugger (dap-mode), integrates with every completion framework, and aims to replicate the full LSP feature set including experimental extensions. It supports all 150+ language servers in the LSP ecosystem with dedicated configuration profiles for each.

Neither philosophy is wrong — they serve different users.


Installation and Initial Setup

Eglot

Eglot is built into Emacs 29+. You only need configuration:

(use-package eglot
  :ensure nil  ; built-in, no need to install
  :hook ((php-mode    . eglot-ensure)
         (web-mode    . eglot-ensure)
         (js-mode     . eglot-ensure)
         (typescript-mode . eglot-ensure))
  :config
  ;; Add intelephense (PHP language server)
  (add-to-list 'eglot-server-programs
               '(php-mode . ("intelephense" "--stdio")))
  ;; Increase timeout for slow servers
  (setq eglot-connect-timeout 30))

Install the PHP language server separately:

npm install -g intelephense
# or
npm install -g @phpactor/language-server

lsp-mode

(use-package lsp-mode
  :ensure t
  :hook ((php-mode    . lsp-deferred)
         (web-mode    . lsp-deferred)
         (js-mode     . lsp-deferred)
         (typescript-mode . lsp-deferred))
  :init
  (setq lsp-keymap-prefix "C-c l")
  :config
  (setq lsp-idle-delay 0.5
        lsp-log-io nil))

(use-package lsp-ui
  :ensure t
  :commands lsp-ui-mode
  :config
  (setq lsp-ui-doc-enable t
        lsp-ui-sideline-enable t
        lsp-ui-peek-enable t))

lsp-mode can auto-install language servers with M-x lsp-install-server, which is a genuine quality-of-life advantage when working across many languages.


Feature Comparison

FeatureEglotlsp-mode
CompletionsVia completion-at-point (Corfu/Company)Via completion-at-point + company-lsp
DiagnosticsFlymake (built-in)Flymake or Flycheck
Hover docsEldoc (built-in tooltip)lsp-ui floating window
Code actionsM-x eglot-code-actionsSideline + modeline + command
Find referencesxref (standard Emacs)xref + lsp-ui-peek overlay
Rename symbolM-x eglot-renamelsp-rename
Inlay hintsYes (Emacs 29+, via Eldoc)Yes (lsp-inlay-hints)
DebuggingNot includeddap-mode (separate package)
Multiple servers per bufferYes (Emacs 29.1+)Yes (lsp-multi-root)
Server auto-installNoYes (lsp-install-server)
Startup performanceFastSlower (more initialisation)
Configuration complexityLowHigh
Built-in to EmacsYes (Emacs 29+)No

Setting Up PHP Development with Eglot and Intelephense

For PHP/Drupal work, Intelephense is the recommended language server — it understands Drupal's coding conventions, provides accurate completions for hooks, and handles large codebases well.

;; Full Eglot + Intelephense setup for PHP/Drupal

(use-package eglot
  :ensure nil
  :hook (php-mode . eglot-ensure)
  :config
  ;; Use Intelephense for PHP
  (add-to-list 'eglot-server-programs
               '(php-mode . ("intelephense" "--stdio")))

  ;; Intelephense settings passed as initializationOptions
  (setq-default eglot-workspace-configuration
                '((:intelephense
                   :stubs ["apache" "bcmath" "bz2" "calendar" "com_dotnet"
                           "Core" "ctype" "curl" "date" "dba" "dom" "enchant"
                           "exif" "FFI" "fileinfo" "filter" "fpm" "ftp" "gd"
                           "gettext" "gmp" "hash" "iconv" "imap" "intl" "json"
                           "ldap" "libxml" "mbstring" "meta" "mysqli" "oci8"
                           "odbc" "openssl" "pcntl" "pcre" "PDO" "pdo_ibm"
                           "pdo_mysql" "pdo_pgsql" "pdo_sqlite" "pgsql"
                           "Phar" "posix" "pspell" "random" "readline" "Reflection"
                           "session" "shmop" "SimpleXML" "snmp" "soap" "sockets"
                           "sodium" "SPL" "sqlite3" "standard" "superglobals"
                           "sysvmsg" "sysvsem" "sysvshm" "tidy" "tokenizer"
                           "xml" "xmlreader" "xmlrpc" "xmlwriter" "xsl" "Zend OPcache"
                           "zip" "zlib" "wordpress" "drupal"]
                   :telemetry (:enabled :json-false)
                   :diagnostics (:enable t)
                   :format (:enable t)))))

The stubs array tells Intelephense which API stubs to load. Adding "drupal" gives you completions for Drupal's global functions and services.

Essential Eglot Keybindings

(use-package eglot
  :bind (:map eglot-mode-map
         ("C-c r"   . eglot-rename)
         ("C-c C-a" . eglot-code-actions)
         ("C-c f"   . eglot-format)
         ("C-c d"   . eldoc)
         ("M-."     . xref-find-definitions)
         ("M-,"     . xref-pop-marker-stack)
         ("M-?"     . xref-find-references)))

Setting Up PHP Development with lsp-mode and Intelephense

(use-package lsp-mode
  :ensure t
  :hook (php-mode . lsp-deferred)
  :init
  (setq lsp-keymap-prefix "C-c l")
  :config
  ;; Intelephense settings
  (setq lsp-intelephense-stubs
        ["drupal" "wordpress" "apache" "bcmath" "Core" "date"
         "dom" "fileinfo" "filter" "hash" "iconv" "json" "mbstring"
         "mcrypt" "meta" "mysqli" "openssl" "pcre" "PDO" "Phar"
         "posix" "readline" "Reflection" "session" "SimpleXML"
         "soap" "sockets" "SPL" "sqlite3" "standard" "tokenizer"
         "xml" "xmlreader" "xmlwriter" "zip" "zlib"]
        lsp-intelephense-telemetry-enabled nil
        lsp-idle-delay 0.3
        lsp-log-io nil))

;; lsp-ui for floating docs and sideline hints
(use-package lsp-ui
  :ensure t
  :config
  (setq lsp-ui-doc-enable t
        lsp-ui-doc-position 'at-point
        lsp-ui-sideline-show-diagnostics t
        lsp-ui-sideline-show-code-actions t
        lsp-ui-peek-always-show t))

Performance in Practice

On a typical Drupal project (several hundred PHP files), the performance difference between Eglot and lsp-mode is noticeable but not dramatic with modern hardware:

  • Cold start: Eglot connects to the language server faster — it does less work on startup. lsp-mode initialises its UI components and workspace scanning in parallel, which can add 1-3 seconds on first open.
  • Completion latency: Both are effectively the same — completion latency is dominated by the language server itself, not the client.
  • Memory: lsp-mode's UI layer adds significant overhead. lsp-ui with sideline enabled continuously polls the server for diagnostics and code actions on every cursor movement.
  • Native compilation: Both benefit enormously from Emacs native compilation (--with-native-compilation). If you are still on interpreted Elisp, this matters more than client choice.

To profile Eglot performance:

;; Enable logging to diagnose slow responses
(setq eglot-events-buffer-size 2000000)
M-x eglot-events-buffer  ; inspect the LSP message log

When to Choose Eglot

  • You want zero external dependencies — Eglot is built in, nothing to install.
  • You already use Corfu/Vertico/Flymake and want seamless integration with the modern Emacs completion stack.
  • You value a clean, predictable configuration you can fully understand.
  • You work primarily in 1-3 languages and do not need automatic server management.
  • You care about startup time and memory footprint (important on slower machines or when running Emacs as a daemon for a long time).

When to Choose lsp-mode

  • You want integrated debugging via dap-mode (Xdebug for PHP, node for JS).
  • You work across many languages and want M-x lsp-install-server to handle server management.
  • You specifically want the lsp-ui floating documentation and sideline diagnostics UX — it genuinely looks closer to VS Code than Eglot's Eldoc approach.
  • Your team or existing config is already on lsp-mode and conversion costs more than it saves.

The Debugger Question

This is where lsp-mode wins outright. If you need an integrated debugger for PHP (Xdebug) or JavaScript inside Emacs, dap-mode is the answer and it is part of the lsp-mode ecosystem:

(use-package dap-mode
  :ensure t
  :after lsp-mode
  :config
  (dap-auto-configure-mode)
  (require 'dap-php))

Eglot has no equivalent. If you use Eglot and want debugging, your options are: switch to lsp-mode+dap-mode, use realgud for PHP/Xdebug (a separate Emacs debugger package), or debug via the terminal.


Summary: The Decision Tree

  • Need integrated Xdebug/DAP debugging? → lsp-mode + dap-mode.
  • Want floating UI docs and sideline diagnostics? → lsp-mode + lsp-ui.
  • Want zero dependencies and a clean config? → Eglot.
  • Already using Corfu + Vertico + Flymake? → Eglot integrates more naturally.
  • Starting fresh on Emacs 29+? → Start with Eglot; switch to lsp-mode only if you hit a specific capability gap.
  • Working across 10+ languages? → lsp-mode's auto-install is worth the overhead.

For most PHP/Drupal developers on Emacs 29+, Eglot with Intelephense is the right starting point. It is fast, well-integrated with the modern Emacs ecosystem, and covers 90% of what a language server is useful for: completions, diagnostics, go-to-definition, rename, and hover docs. Add lsp-mode only when you need what Eglot cannot provide.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.