Copilot.el is an Emacs plugin for GitHub Copilot. It provides inline completions (ghost text), an interactive chat interface, and Next Edit Suggestions — all powered by the official @github/copilot-language-server.
The plugin talks to the Copilot language server over JSON-RPC, using jsonrpc.el
directly rather than going through an LSP client like eglot. This is a
deliberate choice — most of the Copilot protocol is non-standard LSP, and a
single global server is shared across all buffers and projects. See
doc/design.md for the full rationale.
Note
You need access to GitHub Copilot to use this plugin. The service introduced a free tier in early 2025.
copilot.el requires Emacs 27+ and the following packages (installed automatically from MELPA):
editorconfigjsonrpccompattrack-changes
@github/copilot-language-server ships precompiled native binaries for macOS (Apple Silicon & Intel), Linux (x64 & ARM64), and Windows (x64). When npm is not available, copilot-install-server automatically downloads and installs the native binary, so Node.js is not required. If you prefer to install via npm, Node.js 22+ is needed.
(use-package copilot
:ensure t
:hook (prog-mode . copilot-mode)
:bind (:map copilot-completion-map
("<tab>" . copilot-accept-completion)
("TAB" . copilot-accept-completion)
("C-<tab>" . copilot-accept-completion-by-word)
("C-TAB" . copilot-accept-completion-by-word)
("C-n" . copilot-next-completion)
("C-p" . copilot-previous-completion)))Then run M-x copilot-install-server and M-x copilot-login. That's it!
The simplest way to install copilot.el is from MELPA:
(use-package copilot
:ensure t)Or M-x package-install RET copilot RET.
(use-package copilot
:vc (:url "https://github.com/copilot-emacs/copilot.el"
:rev :newest
:branch "main"))straight.el:
(use-package copilot
:straight (:host github :repo "copilot-emacs/copilot.el" :files ("*.el"))
:ensure t)quelpa + quelpa-use-package:
(use-package copilot
:quelpa (copilot :fetcher github
:repo "copilot-emacs/copilot.el"
:branch "main"
:files ("*.el")))Clone this repository, make sure the dependencies listed in Requirements are installed, then:
(add-to-list 'load-path "/path/to/copilot.el")
(require 'copilot)Details
Add package definition to ~/.doom.d/packages.el:
(package! copilot
:recipe (:host github :repo "copilot-emacs/copilot.el" :files ("*.el")))Configure copilot in ~/.doom.d/config.el:
;; accept completion from copilot and fallback to company
(use-package! copilot
:hook (prog-mode . copilot-mode)
:bind (:map copilot-completion-map
("<tab>" . 'copilot-accept-completion)
("TAB" . 'copilot-accept-completion)
("C-TAB" . 'copilot-accept-completion-by-word)
("C-<tab>" . 'copilot-accept-completion-by-word)))Strongly recommend to enable childframe option in company module ((company +childframe)) to prevent overlay conflict.
If pressing tab to complete sometimes doesn't work you might want to bind completion to another key or try:
(after! (evil copilot)
;; Define the custom function that either accepts the completion or does the default behavior
(defun my/copilot-tab-or-default ()
(interactive)
(if (and (bound-and-true-p copilot-mode)
;; Add any other conditions to check for active copilot suggestions if necessary
)
(copilot-accept-completion)
(evil-insert 1))) ; Default action to insert a tab. Adjust as needed.
;; Bind the custom function to <tab> in Evil's insert state
(evil-define-key 'insert 'global (kbd "<tab>") 'my/copilot-tab-or-default))If you would love to configure indentation here, this is an example config that may work for you:
(use-package! copilot
:hook (prog-mode . copilot-mode)
:bind (:map copilot-completion-map
("<tab>" . 'copilot-accept-completion)
("TAB" . 'copilot-accept-completion)
("C-TAB" . 'copilot-accept-completion-by-word)
("C-<tab>" . 'copilot-accept-completion-by-word)
("C-n" . 'copilot-next-completion)
("C-p" . 'copilot-previous-completion))
:config
(add-to-list 'copilot-indentation-alist '(prog-mode 2))
(add-to-list 'copilot-indentation-alist '(org-mode 2))
(add-to-list 'copilot-indentation-alist '(text-mode 2))
(add-to-list 'copilot-indentation-alist '(clojure-mode 2))
(add-to-list 'copilot-indentation-alist '(emacs-lisp-mode 2)))Details
Edit your ~/.spacemacs to include the GitHub Copilot layer this will setup everything for you:
;; ===================
;; dotspacemacs/layers
;; ===================
;; add or uncomment the auto-completion layer
;; add the GitHub Copilot layer
dotspacemacs-configuration-layers
'(
...
auto-completion
github-copilot
...
)For details about the default bindings please refer to the Spacemacs documentation for the github-copilot layer.
After installing the package, run M-x copilot-install-server to install the language server, then M-x copilot-login to authenticate. You can verify everything works with M-x copilot-diagnose (NotAuthorized means you don't have a valid subscription).
Use copilot-mode to automatically provide completions in a buffer:
(add-hook 'prog-mode-hook 'copilot-mode)Or enable it globally with global-copilot-mode:
(global-copilot-mode)To customize when completions trigger, see copilot-enable-predicates and copilot-disable-predicates. To customize when completions are displayed, see copilot-enable-display-predicates and copilot-disable-display-predicates.
Alternatively, you can call copilot-complete manually and use copilot-clear-overlay in post-command-hook to dismiss completions.
copilot-panel-complete opens a *copilot-panel* buffer listing several suggestions for the point at once, sorted by score. Move to the one you want and press RET (or C-c C-c) to insert it back at the position you invoked the command from.
copilot-chat opens an interactive chat with GitHub Copilot using the conversation/* LSP methods. The chat buffer streams responses in real time and automatically provides the current buffer as context.
;; Start a chat (or send a follow-up if one is already open)
M-x copilot-chat
;; Send selected code with an optional prompt
M-x copilot-chat-send-regionFor anything longer than a quick question, copilot-chat-compose (C-c C-e in the chat buffer) pops a dedicated writable buffer where you can draft a multi-line, multi-paragraph message with the usual editing and yank commands, then send it with C-c C-c or cancel with C-c C-k. It sends the same way copilot-chat does: as a new conversation when none exists yet, or as a follow-up turn otherwise. copilot-chat-display (C-c C-d) just shows the existing chat buffer without sending anything.
There are also one-shot task commands that send the active region (or the function at point when no region is active) with a canned prompt, so you don't have to type anything; the answer streams into the chat buffer as usual:
copilot-chat-review— review the code for bugs, risks, and improvementscopilot-chat-fix— fix bugs or problems in the codecopilot-chat-doc— document the code (docstrings and comments)copilot-chat-optimize— optimize the code for performance and readabilitycopilot-chat-write-tests— write unit tests for the code
copilot-chat-task prompts for the task with completion and dispatches to the same machinery. The prompts live in copilot-chat-task-prompts and can be customized (or extended with your own tasks).
Beyond the prose feedback of copilot-chat-review, there is also native Copilot Code Review, the same reviewer behind GitHub's code review. copilot-chat-review-changes sends the repository's uncommitted changes (staged and unstaged, like a pre-commit review) to the dedicated review service, and copilot-chat-review-region does the same for the selected code. Instead of a streamed chat answer, these return structured review comments, rendered in the chat buffer with file, line, an explanation, and a suggested change when there is one. The review runs outside the chat conversation, so it never disturbs one in progress. Note that Copilot Code Review is a separate entitlement; when a subscription doesn't include it, the server's error ("GitHub Copilot Code Review is not enabled.") is shown in the chat buffer.
Key bindings in the *copilot-chat* buffer:
- C-c RET or C-c C-c — send a follow-up message
- C-c C-e — compose a multi-line message in a dedicated buffer
- C-c C-k — cancel streaming, or reset if idle
- C-c C-i — insert the code block at point into the source buffer
- C-c M-w — copy the code block at point to the kill ring
- C-c / — pick and send a slash command (
/explain,/fix,/tests, ...) - C-c C-f — attach a file as context for the next message
- C-c C-d — display the chat buffer without sending a message
- C-c C-w — copy the most recent response to the kill ring
- C-c C-n / C-c C-p — move to the next / previous exchange
- C-c C-r — regenerate the answer to the last request (with a prefix argument, edit it first)
- C-c C-t — rate the current response as good or bad
- q — quit the chat window
Attach extra context for the next message with copilot-chat-add-file-reference (C-c C-f) or copilot-chat-add-region-reference; copilot-chat-clear-references drops anything pending.
copilot-chat-retry regenerates the last answer: it deletes the dead turn on the server and re-sends the request. The original exchange stays put until the new answer arrives and then is replaced, so the buffer ends up with a single clean exchange, and a failed regenerate leaves the original untouched. copilot-chat-rate-response (and the copilot-chat-thumbs-up / copilot-chat-thumbs-down shortcuts) sends good/bad feedback about the current response to GitHub; it is telemetry only and has no local effect.
The chat buffer's header line shows at a glance which mode is active (Agent, InlineAgent, Ask, or a selected custom mode), which model answers, and for an agent-kind mode how many tools are available. Set copilot-chat-show-status-header to nil to hide it; the setting is read when the chat buffer is created, so it takes effect for new chat buffers.
Beyond the copilot-chat-use-agent-mode toggle (which flips between plain Agent and Ask), M-x copilot-chat-select-mode lets you pick any mode the server reports: the built-in Ask, Agent, and InlineAgent, plus any custom project modes. InlineAgent is an agent-kind mode with a restricted tool set aimed at inline editing, so tool-call confirmation, tool registration, and the tool count in the header apply to it just as they do to Agent. The choice takes effect on the next new conversation, since the mode is fixed when a conversation is created; until you pick one, copilot-chat-use-agent-mode still decides between Agent and Ask.
While a reply is being prepared, a small animated indicator is shown under the Copilot: label so the wait before the first streamed chunk no longer looks dead; it disappears as soon as the reply starts (or the turn ends, is cancelled, or errors). Set copilot-chat-show-thinking-indicator to nil to turn it off. While a reply streams the chat window follows the new output only when it is already at the bottom, so you can scroll up to re-read an earlier part of a long response without the stream yanking you back down.
When a turn finishes and you have looked away from the chat, copilot.el raises a desktop notification so you don't have to keep watching it stream. It fires only if the turn took at least copilot-chat-notify-after-seconds (10 by default, nil disables it) and the chat buffer is not the one in your selected window. The backend prefers D-Bus, falls back to osascript on macOS, and to a plain message elsewhere; override it via copilot-chat-notify-function.
Chat sessions can be persisted across Emacs restarts. Set copilot-chat-save-history to t (it is off by default, since transcripts land on disk in plain text) and the whole session is saved after each completed turn, one file per workspace under copilot-chat-history-directory (~/.emacs.d/copilot-chat-history/ by default). M-x copilot-chat-restore brings the saved conversation back: the transcript reappears in the chat buffer, and the next message continues it with the full context (the saved turns are replayed to the server when the new conversation starts). copilot-chat-clear-history deletes the current workspace's saved history; copilot-chat-reset clears only the live session and leaves the file alone.
On a long-running conversation, copilot-chat-compact asks the server to summarize the discussion so far, reclaiming token budget while the conversation continues (the visible transcript is left as-is). The server already compacts automatically as the context fills; this triggers the same summarization on demand and reports how many turns were compacted and the resulting context usage. It is a no-op when there have been no new turns since the last compaction.
copilot-chat-rewrite rewrites the active region according to a free-form instruction (e.g. "make it iterative"). The rewritten code is shown as a diff-style preview against the region and applied only after you confirm, so nothing is changed behind your back; the region is tracked with markers, so edits elsewhere in the buffer while the request is in flight are fine, while edits to the region itself drop the rewrite. The instruction preamble can be customized via copilot-chat-rewrite-prompt, the applied code is re-indented unless copilot-chat-rewrite-indent is set to nil, and a pending rewrite can be cancelled with copilot-chat-stop. Like copilot-chat-insert-commit-message, it runs outside the chat panel and never disturbs an ongoing conversation.
copilot-chat-insert-commit-message generates a commit message from the staged changes and inserts it at point. It is meant to be called from a commit message buffer (e.g. Magit's COMMIT_EDITMSG or any git-commit buffer), but works from any buffer inside a git repository. It uses the language server's native commit-message generator, which also sees your recent commit subjects (so the message matches your repository's style) and the repository's commit instructions. It runs outside the chat panel, so it never disturbs an ongoing conversation. On a server too old to provide the native generator, it falls back to a one-shot chat whose instruction can be customized via copilot-chat-commit-message-prompt.
Customization:
copilot-chat-model— model to use for chat (defaultnil, meaning a default chat model is resolved from the server)copilot-chat-use-agent-mode— let Copilot run tools (shell commands, file edits, reads, etc.) during a chat (defaultnil)copilot-chat-preview-tool-edits— in agent mode, preview file changes in a temporary buffer before you confirm an edit tool (defaultt)copilot-chat-terminal-timeout— seconds before an agent-moderun_in_terminalcommand is killed (default30,nilto disable)copilot-chat-ripgrep-program— ripgrep executable used for agent-mode workspace search (default"rg")copilot-chat-auto-approve-tools— tool names that skip the confirmation prompt (default'("get_errors"))copilot-chat-save-history— save the chat transcript to disk after each turn, forcopilot-chat-restore(defaultnil)copilot-chat-history-directory— where saved transcripts live, one file per workspace (default~/.emacs.d/copilot-chat-history/)copilot-chat-presets— named bundles of chat settings you can switch between withcopilot-chat-apply-preset(defaultnil)copilot-chat-show-thinking-indicator— animate a thinking indicator while a reply is being prepared (defaultt)copilot-chat-notify-after-seconds— notify when a turn finishes and you have looked away, but only if it ran at least this long (default10,nildisables)copilot-chat-notify-function— function called with a title and body to raise the notification (defaultcopilot-chat--notify)
If you often flip between a couple of setups (say a quick ask-mode model and a tool-capable agent-mode one), collect them as presets and switch with M-x copilot-chat-apply-preset. A preset is a plist that may set :model, :agent-mode, and :auto-approve-tools; any key you leave out is untouched. A preset's model takes effect on your next message (the model is sent with every turn, so even the current conversation switches), while agent mode takes effect on the next new conversation, since it is fixed when a conversation is created.
(setopt copilot-chat-presets
'(("fast" . (:model "gpt-4o" :agent-mode nil))
("agent" . (:model "gpt-5-codex" :agent-mode t
:auto-approve-tools ("get_errors" "copilot.read_file")))))At each tool confirmation prompt you can answer yes, no, or always; always approves that tool for the rest of the conversation so it stops asking.
For the tools copilot.el runs itself, the prompt also offers edit, which lets you change the input before the tool runs: the shell command of run_in_terminal, the content of create_file, or the URLs of fetch_web_page. Each opens in a temporary buffer, confirmed with C-c C-c or cancelled with C-c C-k. The tool then runs once with your edited input. Server-executed and MCP tools do not offer edit, since the server does not accept a modified input back.
Important
Agent mode only works with a model that can call tools. Some chat models (and whatever default the server resolves when copilot-chat-model is nil) can't, and then Copilot just describes the commands to run instead of running them, so agent mode looks inactive. Pick a tool-capable model with M-x copilot-chat-select-model. copilot.el also warns when it starts an agent-mode conversation whose model lacks tool support.
Tip
Install markdown-mode for rich markdown rendering (headings, code blocks, emphasis, etc.) in the chat buffer. Without it, only basic highlighting is used.
By default the chat buffer is rendered as GitHub-flavored Markdown. Org users can set copilot-chat-frontend to org to render it as Org instead: each exchange becomes a foldable * You heading with a nested ** Copilot heading, the buffer uses Org's own highlighting, and outline-minor-mode is enabled so turns fold. This frontend needs the org library (bundled with Emacs). The setting is read when the chat buffer is created, so it applies to new chat buffers. Note that only the scaffolding changes: the streamed assistant response is kept exactly as the server sends it, so its body stays markdown-ish (fenced code blocks and all) under either frontend, and the code-block commands keep working. Because the body is left as-is, a reply line that begins with * renders as an Org heading, as it would in any Org buffer.
When agent mode is enabled, you can extend Copilot with Model Context Protocol servers. Set copilot-mcp-servers to a map of server name to definition and copilot.el forwards it to the language server, whose tools then become available in the chat. Each invocation still prompts for confirmation unless listed in copilot-chat-auto-approve-tools.
(setopt copilot-mcp-servers
'(:fetch (:command "uvx" :args ["mcp-server-fetch"])
:memory (:command "npx"
:args ["-y" "@modelcontextprotocol/server-memory"])
:remote (:type "http" :url "https://example.com/mcp/"
:headers (:Authorization "Bearer TOKEN"))))A server with a :command is launched locally over stdio; one with a :type of "http" or "sse" is reached at its :url. The value is serialized to JSON, so list-valued fields like :args must be vectors. Use setopt (or customize) so a running server picks up the change.
Run M-x copilot-chat-list-mcp-tools to see the connected servers, their status, and the tools they expose. A server that fails to start is reported as a warning.
In agent mode, Copilot can search the project: copilot.el answers the server's file-glob, text-search, file-read, and directory-list requests using ripgrep (so .gitignore is honored). Make sure rg is on your PATH, or point copilot-chat-ripgrep-program at it.
Set copilot-chat-enable-semantic-search to t to also let the server build a semantic index of the workspace, for whole-codebase ("@workspace"-style) questions. Indexing computes embeddings server-side, so it uses CPU and network; it is off by default and takes effect when the server next starts.
When a conversation gets delegated to GitHub's cloud coding agent (the server-side agent that continues work in a pull request), the server reports its progress to the editor. copilot.el echoes each update and records it, with the PR link, in the *copilot-coding-agent* buffer.
The language server reads repository instruction files on its own and applies them to chat and agent requests; copilot.el sends the workspace folders, so this works out of the box. The recognized files (same as in VS Code):
.github/copilot-instructions.md— project-wide instructions, always appliedAGENTS.md— applied always, including nestedAGENTS.mdfiles in subdirectoriesCLAUDE.md/CLAUDE.local.md— also honored, if you share a repo with Claude users.github/instructions/*.instructions.md— scoped instructions withapplyToglob front matter.github/git-commit-instructions.md— used when generating commit messages, including bycopilot-chat-insert-commit-message
Instruction files are enabled by default. To turn them off, set the corresponding server option through copilot-lsp-settings:
(setopt copilot-lsp-settings
'(:github (:copilot (:chat (:codeGeneration (:useInstructionFiles :json-false))))))For a more feature-rich chat experience, take a look at copilot-chat.el.
Warning
copilot-chat.el (the chep package) and copilot.el both provide an Emacs feature called copilot-chat, so they cannot be installed at the same time. Having both will cause autoload errors such as "failed to define function copilot-chat-display". If you want to use the chat built into copilot.el, make sure chep/copilot-chat.el is uninstalled, and vice versa.
NES predicts the next edit you'll want to make anywhere in the file, based on your recent editing patterns. Unlike inline completions (ghost text at the cursor), NES suggestions can replace or delete existing text at any location.
Note
NES requires copilot-language-server version 1.434.0 or newer. Run M-x copilot-reinstall-server to upgrade if needed.
copilot-nes-mode works alongside copilot-mode and relies on it to start and sync the language server. Enable both in the buffer; on its own copilot-nes-mode does not produce any suggestions (it will tell you so when enabled without copilot-mode).
(add-hook 'prog-mode-hook #'copilot-mode)
(add-hook 'prog-mode-hook #'copilot-nes-mode)When a suggestion is pending:
- TAB — accept the suggestion (jumps to it first if far away, applies on second press)
- C-g — dismiss the suggestion
Note
Both copilot-nes-mode and copilot-mode ship default keybindings: NES predefines TAB and C-g, and copilot-mode predefines TAB (accept) plus a few more in copilot-completion-map (see Keybindings). In both cases the bindings only take effect while a suggestion (or completion) is pending and otherwise fall through to their usual commands, so they are safe to leave enabled.
Customization variables:
copilot-nes-idle-delay— seconds of idle time before requesting a suggestion (default0.5)copilot-nes-auto-dismiss-move-count— cursor movements before auto-dismissing (default3)copilot-nes-auto-dismiss-distance— max lines between point and suggestion before auto-dismissing (default40)
copilot-mode binds these keys in copilot-completion-map, active only while a completion overlay is visible (they otherwise fall through to their usual commands):
(keymap-set copilot-completion-map "<tab>" #'copilot-accept-completion)
(keymap-set copilot-completion-map "TAB" #'copilot-accept-completion)
(keymap-set copilot-completion-map "C-<tab>" #'copilot-accept-completion-by-word)
(keymap-set copilot-completion-map "C-TAB" #'copilot-accept-completion-by-word)
(keymap-set copilot-completion-map "M-n" #'copilot-next-completion)
(keymap-set copilot-completion-map "M-p" #'copilot-previous-completion)To change them, rebind the keys you want or clear the map, for example (keymap-unset copilot-completion-map "TAB"). The alternatives below are drop-in replacements.
If you use company-mode or corfu, TAB is already taken. An alternative inspired by Fish shell avoids the conflict entirely — right-arrow accepts, and forward-word/end-of-line accept partially. Unbind the default TAB first, then:
(keymap-unset copilot-completion-map "<tab>")
(keymap-unset copilot-completion-map "TAB")
(keymap-set copilot-completion-map "<right>" #'copilot-accept-completion)
(keymap-set copilot-completion-map "C-f" #'copilot-accept-completion)
(keymap-set copilot-completion-map "M-<right>" #'copilot-accept-completion-by-word)
(keymap-set copilot-completion-map "M-f" #'copilot-accept-completion-by-word)
(keymap-set copilot-completion-map "C-e" #'copilot-accept-completion-by-line)
(keymap-set copilot-completion-map "<end>" #'copilot-accept-completion-by-line)
(keymap-set copilot-completion-map "M-n" #'copilot-next-completion)
(keymap-set copilot-completion-map "M-p" #'copilot-previous-completion)To remap the built-in zap commands automatically whenever the overlay is visible:
(keymap-set copilot-completion-map "<remap> <zap-to-char>" #'copilot-accept-completion-to-char)
(keymap-set copilot-completion-map "<remap> <zap-up-to-char>" #'copilot-accept-completion-up-to-char)You can configure the underlying LSP settings via copilot-lsp-settings. The complete list of available options can be found
here.
Here we set the GitHub Enterprise server to https://example2.ghe.com, exchange the URL with your own server.
(setopt copilot-lsp-settings '(:github-enterprise (:uri "https://example2.ghe.com"))) ;; allows changing the value without restarting the LSP
(setq copilot-lsp-settings '(:github-enterprise (:uri "https://example2.ghe.com"))) ;; alternativelyYou have to restart the LSP (M-x copilot-diagnose) when using setq to change the value. When logging in, the URL for the authentication flow should be the same as the one set in copilot-lsp-settings.
Copilot.el detects the programming language of a buffer based on the major-mode
name, stripping the -mode part. The resulting languageId should match the table
here.
You can add unusual major-mode mappings to copilot-major-mode-alist. Without
the proper language set suggestions may be of poorer quality.
(add-to-list 'copilot-major-mode-alist '("enh-ruby" . "ruby"))Format: '(:host "127.0.0.1" :port 7890 :username "user" :password "password"), where :username and :password are optional.
For example:
(setq copilot-network-proxy '(:host "127.0.0.1" :port 7890))If your network runs a TLS-inspecting proxy or firewall (Zscaler and the like) or uses a self-signed certificate, the language server's own HTTPS connection to GitHub can fail. This usually shows up as copilot-login reporting a timeout (Authentication failure: Timed out), because the server never manages to reach GitHub. There are two ways to handle it.
Point the language server at your proxy and, when it intercepts TLS, tell it not to verify the proxy's certificate:
(setq copilot-network-proxy '(:host "127.0.0.1" :port 7890 :rejectUnauthorized :json-false))Or make the server trust your CA by exporting NODE_EXTRA_CA_CERTS (pointing at your CA chain in PEM format) before Emacs starts, since the server reads it only when it launches:
export NODE_EXTRA_CA_CERTS=/path/to/ca-chain.pem
emacsSetting it from your Emacs config with (setenv "NODE_EXTRA_CA_CERTS" "...") works too, as long as it happens before the server process starts; restart the server (or Emacs) afterward. Note that this is a TLS issue in the language server's HTTP client, not something copilot.el can resolve on its own.
copilot-on-request registers a handler for incoming JSON-RPC requests from the language server. Return a JSON-serializable value as the result, or call jsonrpc-error for errors. Read more.
;; Display desktop notification if Emacs is built with D-Bus
(copilot-on-request
'window/showMessageRequest
(lambda (msg) (notifications-notify :title "Emacs Copilot" :body (plist-get msg :message))))copilot-on-notification registers a listener for incoming LSP notifications.
(copilot-on-notification
'window/logMessage
(lambda (msg) (message (plist-get msg :message))))M-x copilot-menu opens a transient menu (in the style of Magit) that puts the
most common commands one keystroke away: completions, chat, agent mode, account
and usage info, and server management. It also shows the current state of
copilot-mode, agent mode, and the selected chat model, so it doubles as a
quick status overview.
The menu is built on the transient package, which is bundled with Emacs 28.1
and newer. It is a soft dependency: on Emacs 27.2 (where current transient
releases are not installable) the rest of copilot.el works as usual and
copilot-menu explains what is missing if invoked.
Tip
You don't need to memorize the list — just type M-x copilot- followed by TAB, use M-x copilot-menu, or use the Copilot menu in the menubar.
| Command | Description |
|---|---|
copilot-menu |
Open a transient menu with the most common commands |
| Setup | |
copilot-install-server |
Install the language server |
copilot-uninstall-server |
Remove the installed language server |
copilot-reinstall-server |
Re-install the language server |
copilot-login |
Log in to GitHub Copilot |
copilot-logout |
Log out from GitHub Copilot |
copilot-diagnose |
Restart the server and show diagnostic info |
copilot-quota |
Show the current Copilot usage quota |
copilot-list-code-references |
Show suggestions that matched public code |
copilot-select-completion-model |
Choose which model to use for completions |
copilot-chat-select-model |
Choose which model to use for chat |
copilot-chat-select-mode |
Choose the chat mode (Ask, Agent, InlineAgent, ...) |
copilot-chat-byok-add-key |
Save your own API key for a model provider (BYOK) |
copilot-chat-byok-add-custom-provider |
Register a custom OpenAI-compatible BYOK endpoint |
copilot-chat-byok-add-model |
Register a BYOK model to use in chat |
copilot-chat-byok-list |
List your registered BYOK models |
copilot-chat-byok-list-custom-providers |
List your configured custom BYOK providers |
copilot-chat-byok-remove-model |
Remove a registered BYOK model |
copilot-chat-byok-remove-key |
Delete a provider's stored BYOK API key |
| Completion | |
copilot-mode |
Toggle automatic completions in the current buffer |
copilot-complete |
Trigger a completion at point |
copilot-panel-complete |
Show a panel buffer with multiple completion suggestions |
copilot-panel-accept-completion |
Insert the panel solution at point (RET/C-c C-c in the panel) |
copilot-accept-completion |
Accept the current completion |
copilot-accept-completion-by-word |
Accept the next N words (prefix arg) |
copilot-accept-completion-by-line |
Accept the next N lines (prefix arg) |
copilot-accept-completion-by-sentence |
Accept the next N sentences (prefix arg) |
copilot-accept-completion-by-paragraph |
Accept the next N paragraphs (prefix arg) |
copilot-accept-completion-to-char |
Accept through a character (inclusive, like zap-to-char) |
copilot-accept-completion-up-to-char |
Accept up to a character (exclusive, like zap-up-to-char) |
copilot-clear-overlay |
Dismiss the completion overlay |
| Navigation | |
copilot-next-completion |
Cycle to the next completion |
copilot-previous-completion |
Cycle to the previous completion |
| Chat | |
copilot-chat |
Open Copilot Chat and send a message |
copilot-chat-compose |
Draft a multi-line message in a dedicated buffer and send it |
copilot-chat-display |
Display the chat buffer without sending a message |
copilot-chat-send |
Send a follow-up message in the current chat |
copilot-chat-send-region |
Send the selected region as context with an optional prompt |
copilot-chat-task |
Pick a one-shot task and run it on the region or defun at point |
copilot-chat-review |
Review the region or defun at point |
copilot-chat-fix |
Fix the region or defun at point |
copilot-chat-doc |
Document the region or defun at point |
copilot-chat-optimize |
Optimize the region or defun at point |
copilot-chat-write-tests |
Write tests for the region or defun at point |
copilot-chat-rewrite |
Rewrite the region per an instruction, with a diff preview and confirmation |
copilot-chat-review-changes |
Review the uncommitted changes with native Copilot Code Review |
copilot-chat-review-region |
Review the selected code with native Copilot Code Review |
copilot-chat-stop |
Cancel streaming, or reset the conversation if idle |
copilot-chat-reset |
Destroy the current conversation and clear the chat buffer |
copilot-chat-compact |
Compact the conversation on the server to reclaim context |
copilot-chat-restore |
Restore the saved chat for the current workspace |
copilot-chat-clear-history |
Delete the saved chat history for the current workspace |
copilot-chat-insert-commit-message |
Generate a commit message for the staged changes and insert it at point |
copilot-chat-apply-preset |
Switch to a named bundle of chat settings from copilot-chat-presets |
| Next Edit Suggestions | |
copilot-nes-mode |
Toggle NES in the current buffer |
copilot-nes-accept |
Accept (or jump to) the current NES suggestion |
copilot-nes-dismiss |
Dismiss the current NES suggestion |
Tip
Use M-x customize-group RET copilot to browse all available
configuration options and their documentation.
A few commonly tweaked variables:
copilot-idle-delay— Seconds to wait before triggering completion (default0). Set tonilto disable automatic completion entirely.copilot-lsp-server-version— Pin a specific @github/copilot-language-server version (nilmeans latest).copilot-enable-predicates/copilot-disable-predicates— Control whencopilot-moderequests completions.copilot-enable-display-predicates/copilot-disable-display-predicates— Control when completions are shown.copilot-clear-overlay-ignore-commands— Commands that won't dismiss the overlay.copilot-indentation-alist— Override indentation width per major mode.copilot-enable-parentheses-balancer— Post-process completions to balance parentheses in Lisp modes (defaultt). Set tonilto use raw server completions.
copilot.el communicates with @github/copilot-language-server over JSON-RPC using the LSP protocol plus Copilot-specific extensions. The table below shows the current coverage.
| Method | Status | Notes |
|---|---|---|
initialize |
Supported | Sends rootUri, workspaceFolders, and editor info |
shutdown |
Supported | Clean shutdown before exit |
textDocument/inlineCompletion |
Supported | Core completion mechanism |
workspace/executeCommand |
Supported | Executes server-side commands on accept |
signInInitiate / signInConfirm / checkStatus / signOut |
Supported | Authentication flow |
copilot/models |
Supported | Lists available completion models |
getPanelCompletions |
Supported | Multiple suggestions in a panel buffer |
conversation/create |
Supported | Start a new chat conversation |
conversation/turn |
Supported | Send a follow-up chat message |
conversation/destroy |
Supported | End a chat conversation |
copilot/codeReview/reviewChanges |
Supported | Native code review of the uncommitted changes |
copilot/codeReview/reviewSnippets |
Supported | Native code review of a selection |
textDocument/copilotInlineEdit |
Supported | Next Edit Suggestions (NES) |
| Method | Status | Notes |
|---|---|---|
initialized |
Supported | |
exit |
Supported | Sent after shutdown |
textDocument/didOpen |
Supported | |
textDocument/didClose |
Supported | |
textDocument/didChange |
Supported | Incremental sync |
textDocument/didFocus |
Supported | |
textDocument/didShowCompletion |
Supported | Telemetry when overlay is displayed |
textDocument/didPartiallyAcceptCompletion |
Supported | Telemetry for partial acceptance |
textDocument/didShowInlineEdit |
Supported | Telemetry when NES overlay is displayed |
workspace/didChangeConfiguration |
Supported | Sent on settings change |
workspace/didChangeWorkspaceFolders |
Supported | Dynamic workspace roots |
$/cancelRequest |
Supported | Cancels stale completion requests |
textDocument/didSave |
Not yet | |
notebookDocument/* |
Not yet |
| Method | Status | Notes |
|---|---|---|
window/showMessageRequest |
Supported | Prompts via completing-read |
window/showDocument |
Supported | Opens URIs in browser or Emacs |
conversation/context |
Supported | Provides editor context for chat |
copilot/codingAgentMessage |
Supported | Cloud coding agent updates, logged to *copilot-coding-agent* |
| Method | Status | Notes |
|---|---|---|
window/logMessage |
Supported | Logged to *copilot-language-server-log* |
didChangeStatus |
Supported | Shown in mode-line |
$/progress |
Supported | Progress shown in mode-line |
PanelSolution / PanelSolutionsDone |
Supported | Panel completion results |
Extensible via copilot-on-request and copilot-on-notification for any messages not handled above.
This is an example of using together with the default frontend of
company-mode. Because both company-mode and copilot.el use overlays to show
completions, the conflict is inevitable. The recommended solution is to use
company-box (only available on GUI), which is based on child frames rather than
overlays.
After using company-box, you get:
In other editors (e.g. VS Code, PyCharm), completions from Copilot and other sources cannot show at the same time.
In copilot.el they are allowed to coexist, so you can choose the better one at any time.
If you are using whitespace-mode, make sure to remove newline-mark from whitespace-style.
Not necessarily. GitHub introduced a free tier for Copilot in early 2025 that includes a limited number of completions per month. A paid subscription (Individual or Business) removes these limits.
This is usually caused by another package binding TAB in a way that takes
priority. Common culprits include company-mode, corfu, yasnippet, and Evil
mode. A few things to check:
- Bind both
<tab>andTAB. In GUI Emacs these are different events —<tab>is the function key andTABis theC-icharacter. Some modes only intercept one of them. The Quick Start example binds both. - Use the fish-style keybindings. If TAB is hopelessly taken by another
package, bind acceptance to
<right>/C-finstead. See Fish-style keybindings. - Doom Emacs users — see the Doom Emacs installation section for a workaround using a custom Evil insert-state binding.
Yes. You can call M-x copilot-complete manually in any buffer — it will start
the server and open the document automatically. Use copilot-clear-overlay (or
simply type) to dismiss the suggestion. This is useful if you prefer on-demand
completions rather than automatic ones.
A few things to try:
- Run
M-x copilot-diagnose— it restarts the server and prints diagnostic info. Look forNotAuthorized(subscription issue) or connection errors. - Check your network — the language server needs to reach GitHub's API. If
you're behind a proxy, configure
copilot-network-proxy. - Large files — buffers over
copilot-max-charcharacters (default 100 000) are skipped. You can raise the limit, but very large files will always be slower. - Tune
copilot-idle-delay— the default is0(immediate). A small delay (e.g.0.2) reduces server load when typing quickly.
Use copilot-disable-predicates to add functions that return t when Copilot
should stay quiet:
;; Disable in org-mode
(add-to-list 'copilot-disable-predicates
(lambda () (derived-mode-p 'org-mode)))Or simply don't add copilot-mode to the hooks of modes you want to exclude.
If you use global-copilot-mode, the predicate approach is the way to go.
Copilot.el includes a parentheses balancer that post-processes completions in
Lisp modes (emacs-lisp-mode, clojure-mode, scheme-mode, etc.) to fix
unbalanced delimiters. It is enabled by default. If you still see issues,
make sure copilot-enable-parentheses-balancer is t, or file a bug report
with the completion text and buffer context.
If the balancer is causing problems for your workflow, you can disable it:
(setopt copilot-enable-parentheses-balancer nil)Run M-x copilot-select-completion-model to interactively choose from the
models available on your subscription. The selection is saved in
copilot-completion-model. Set it to nil to revert to the server default.
Note: Only models with a "completion" scope are available for inline
completions. Models like Claude, Gemini, and others that you may see in VS
Code's chat UI are chat/edit-only and cannot be used for inline completions.
This is a server-side limitation, not a copilot.el restriction. Currently
GitHub only offers a single completion model (gpt-41-copilot).
To select a chat model, run M-x copilot-chat-select-model. Many more models
are available for chat (Claude, Gemini, GPT-4o, etc.). The selection is saved
in copilot-chat-model. When that is left at nil, copilot.el resolves a
default from the server (its designated chat default, or an auto model)
rather than leaving the model unset, which some servers answer with an empty
reply.
The completion list flags premium models (billed against your premium-request quota) and policy-locked ones (models whose provider terms you have not yet accepted). Selecting a policy-locked model prompts you to accept its terms and enables it on the server before switching, so you no longer have to leave Emacs to unlock a model in the GitHub settings.
You can use your own model-provider API keys (Anthropic, OpenAI, Gemini, Groq, OpenRouter, Azure) in chat instead of Copilot's models:
copilot-chat-byok-add-keysaves a provider's API key. The key is read without echoing and handed to the language server, which stores it; copilot.el never keeps or displays it. (Azure keys are stored per model, so it asks for a model id.)copilot-chat-byok-add-modelregisters a model for that provider (id, display name, and whether it supports tool calls and vision; Azure also needs a deployment URL).- The model then shows up in
copilot-chat-select-model, tagged[BYOK: PROVIDER]. Selecting it routes your chat turns to that provider using your key.
copilot-chat-byok-list shows what you have registered, and
copilot-chat-byok-remove-model / copilot-chat-byok-remove-key remove a model
or a provider's key.
Beyond the built-in providers you can point BYOK at any OpenAI-compatible
endpoint. copilot-chat-byok-add-custom-provider registers one under a name of
your choice (that is not a built-in provider), along with its API type
(chatCompletions, responses, or messages) and key.
copilot-chat-byok-add-model then offers your custom providers alongside the
built-in ones; a custom provider's models each need their endpoint's deployment
URL. copilot-chat-byok-list-custom-providers lists the custom providers you
have configured.
Using a BYOK model requires a BYOK-eligible Copilot account; without one the server rejects the turn with a "BYOK is disabled for this account" error. Registering keys and models works regardless.
Note
If you turn on protocol logging (set copilot-log-max to a positive value),
the JSON-RPC request bodies, including a BYOK key you are saving, are written
to the *copilot events* buffer in plaintext. Keep logging off (the default)
when saving keys.
Copilot can detect when a suggestion closely matches publicly available code. GitHub calls these matches code references (or "code referencing"), and copilot.el uses the same term throughout.
Note
"Code references" here are GitHub's public-code matches. They have nothing to
do with Emacs xref (jumping to definitions and finding references to a
symbol). We follow GitHub's naming so the feature is easy to recognize, but
the overlap with xref is purely a coincidence of vocabulary.
When copilot-show-code-references is non-nil (the default), such matches are
announced in the echo area and collected, with their licenses and source URLs,
in a buffer you can open with M-x copilot-list-code-references. Set the option
to nil to turn the feature off, including the underlying server traffic.
- Make sure you have restarted your Emacs (and rebuild the plugin if necessary) after updating the plugin.
- Async request errors (e.g. cancelled completions) are logged to
*Messages*automatically. Check there first for clues. - For deeper investigation, enable event logging by customizing
copilot-log-max(to e.g. 1000) and enable debug log(setq copilot-server-args '("--stdio" "--debug")), then paste related logs from the*copilot events*,*copilot stderr*and*copilot-language-server-log*buffers. - If an exception is thrown, please also paste the stack trace (use
M-x toggle-debug-on-errorto enable stack trace).
See doc/design.md for an overview of the architecture and key design decisions.
Unit tests (requires eask):
eask test buttercupeask lint checkdoc
eask lint indentThere's a manual integration test in dev/integration-smoke.el that connects to the real Copilot language server and verifies the textDocument/inlineCompletion round-trip. It requires the server to be installed and authenticated.
emacs --batch -L . -l dev/integration-smoke.elcopilot.el was started in March 2022. At the time there was no public Copilot server package — the only way to get one was to extract the Node.js agent bundled inside copilot.vim. Early versions of copilot.el reverse-engineered the agent's JSON-RPC protocol from copilot.vim and shipped a copy of that agent in the repository, updating it whenever a new copilot.vim release appeared.
In early 2025, GitHub published the official
@github/copilot-language-server on npm. copilot.el migrated to this open
server, dropping the copilot.vim dependency entirely. The switch also enabled
newer protocol features like textDocument/inlineCompletion (replacing the
legacy getCompletions), Copilot Chat, and Next Edit Suggestions.
These projects helped make copilot.el possible:
- copilot.vim — the original reference implementation whose bundled agent powered copilot.el for its first three years
- https://github.com/TommyX12/company-tabnine/ — inspiration for the overlay-based completion UX
- https://github.com/cryptobadger/flight-attendant.el
- @github/copilot-language-server
Current maintainer: @bbatsov.
Retired maintainers: @zerolfx, @emil-vdw, @jcs090218, @rakotomandimby.
copilot.el is built and maintained by volunteers in their spare time. If you find it useful,
please consider supporting its continued development.
Here are the ways in which you can support the project:
- GitHub Sponsors (recurring or one-time donations)
- Patreon (recurring donations)
- PayPal (one-time donations)
copilot.el is distributed under the MIT license.
Copyright © 2022-2026 copilot-emacs maintainers and contributors.


