What Are Emacs Code Snippets
Code snippets in Emacs are predefined templates that expand a short trigger word or key binding into a larger block of code. When you type a snippet trigger and press a designated expansion key, the snippet replaces the trigger with its full content, often positioning the cursor at logical insertion points so you can fill in variable parts without breaking flow.
Unlike simple abbreviation expansions, snippets can include field stops, default values, mirrored regions, and embedded Elisp expressions. This means a snippet for a for loop doesn't just dump static text — it can ask you for the loop variable, insert it in multiple places simultaneously, and even compute the upper bound based on a variable you entered earlier.
The most widely adopted snippet engine for Emacs is YASnippet (Yet Another Snippet system). It comes in two parts: a collection of ready-made snippets for dozens of languages, and the engine itself that powers expansion, field navigation, and snippet management. There is also built-in functionality like abbrev and tempo (or its modern successor skeleton) that can serve simpler snippet needs, but YASnippet remains the gold standard for serious snippet-driven development.
Why Snippets Matter in Your Workflow
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Every keystroke you eliminate is cognitive load you reclaim. Snippets address this at the source by letting you express intent rather than mechanics. Here is why they are indispensable:
- Speed — Typing
cland hittingTabto produce a fullconsole.logstatement is measurably faster than typing it out character by character. - Consistency — Teams can distribute snippet files so everyone produces identically structured boilerplate — license headers, error handling blocks, or test scaffolds.
- Reduced errors — Mirroring fields ensures that if you change a variable name in one location, it updates everywhere else in the snippet automatically. No more forgetting to rename a shadowed variable.
- Discoverability — Snippet menus (via
yas-describe-tablesor completion frameworks likecompany-mode) show you what expansions are available, serving as a live reference for language idioms you might otherwise forget. - Focus — By offloading mechanical typing to the editor, you stay in the problem-solving mindset rather than the transcription mindset.
Setting Up YASnippet
YASnippet is available on MELPA. The canonical setup uses use-package for clean configuration:
(use-package yasnippet
:ensure t
:config
(yas-global-mode 1))
If you also want the bundled snippet collection (snippets for Python, JavaScript, Ruby, C++, and many more), install yasnippet-snippets separately:
(use-package yasnippet-snippets
:ensure t
:after yasnippet)
Once installed, a snippet expands when you type its trigger and press Tab (bound to yas-expand). If multiple snippets match the trigger, Tab cycles through them. You can also invoke expansion explicitly with M-x yas-expand.
Key Bindings You Need
Tab— Expand trigger or navigate to next field stopS-Tab— Navigate to previous field stopC-c & C-s— Create a new snippet from the active region or bufferC-c & C-n— Create a new snippet by writing it in a dedicated bufferM-x yas-new-snippet— Interactive snippet creationM-x yas-visit-snippet-file— Edit an existing snippet by name
Your First Snippet: Writing and Using It
Let's create a snippet for a React functional component. First, invoke M-x yas-new-snippet. Emacs opens a new buffer in snippet-mode. Write:
# -*- mode: snippet -*-
# name: React functional component
# key: rfc
# expand-env: ((yas-indent-line 'fixed))
# --
import React from 'react';
const ${1:ComponentName} = ({ ${2:props} }) => {
return (
<div>
$0
</div>
);
};
export default ${1:$(yas-field-value 1)};
Now save this with C-c C-c (or M-x yas-load-snippet-buffer). You'll be prompted for a snippet name. Save it as rfc in the react-mode snippet directory (or let YASnippet prompt you for the target mode).
Now in any react-mode buffer, typing rfc followed by Tab expands to the full component skeleton. The cursor lands on ComponentName (field ${1}). Type MyButton, press Tab, and the cursor jumps to props (field ${2}). Notice that export default ${1:$(yas-field-value 1)} mirrors whatever you typed in field 1 — so export default MyButton appears automatically. Press Tab once more to reach the final field $0 inside the div.
Anatomy of a Snippet Definition
- Header section — Lines before
# --contain metadata.# name:is a human-readable label;# key:is the trigger word;# expand-env:can set expansion-time variables;# condition:restricts expansion (e.g., only inside strings). - Body section — Everything after
# --is the template text that replaces the trigger. - Field syntax —
${N:default}defines a tab stop.$N(without braces) is a bare field.$0is the exit point (where the cursor lands after the finalTab). - Mirror syntax —
${N:$(some-elisp)}executes Elisp code and inserts the result.yas-field-valuefetches the current value of another field, enabling mirroring. - Choices —
${N:choice1|choice2|choice3}presents a menu when you reach that field.
Built-in Snippet Features Deep Dive
Field Transforms with Embedded Elisp
You can run arbitrary Emacs Lisp inside a field definition. This snippet for a Java getter method demonstrates field transformation:
# -*- mode: snippet -*-
# name: Java getter
# key: get
# --
public ${1:String} get${1:$(capitalize yas-text)}() {
return this.${2:field};
}
${3:$(when (eq (yas-field-value 1) "boolean") "// is-style getter for boolean")}
Field 1 asks for the type (defaulting to String). The method name is constructed by prepending get to a capitalized version of whatever you type. Field 3 uses a conditional to emit a comment only when the type is boolean.
Nested Field Stops and Groups
Sometimes you want multiple cursor positions within the same logical field. Use ${N:default} with identical field numbers:
# -*- mode: snippet -*-
# name: Python function with docstring
# key: defd
# --
def ${1:function_name}(${2:parameters}):
"""${1:function_name} — ${3:description}"""
$0
Here field 1 appears twice: once in the function definition and once in the docstring. Typing the function name once updates both locations. This is simpler than using yas-field-value for same-field mirroring.
Snippet Conditions
You can restrict a snippet to expand only when certain conditions are met. The # condition: header expects Elisp that returns non-nil:
# -*- mode: snippet -*-
# name: console.log inside function
# key: cl
# condition: (yas-in-string-p)
# --
console.log(`${1:variable}:`, ${1:$(yas-field-value 1)});
This snippet only expands when the cursor is inside a string literal — useful for avoiding accidental expansion in normal code.
Organizing Snippets: Directory Structure
YASnippet looks for snippets in directories listed in yas-snippet-dirs. The typical layout is:
~/.emacs.d/snippets/
├── python-mode/
│ ├── defd # trigger: defd
│ ├── for # trigger: for
│ └── try-except # trigger: try
├── js2-mode/
│ ├── rfc
│ └── useef
└── org-mode/
├── srcblock
└── table
Each subdirectory corresponds to a major mode name (or an alias defined via yas-alias-to-buffer-version). The filename (without extension) becomes the default trigger key, though you can override it with # key: in the snippet header.
You can also group shared snippets in a directory named fundamental-mode (or any mode from which other modes derive), and YASnippet will make them available in derived modes as well.
Multiple Snippet Directories
For team projects, you can add project-local snippet directories:
(setq yas-snippet-dirs
'("~/.emacs.d/snippets"
"~/projects/team-snippets"
"~/projects/personal-snippets"))
Use M-x yas-recompile-all after changing directory contents to rebuild the snippet tables. For a single directory, M-x yas-recompile suffices.
Creating Snippets from Existing Code
Mark a region of code you want to turn into a snippet, then press C-c & C-s. YASnippet prompts for a snippet name and trigger key, then opens the snippet buffer with the region's content pre-populated. You then refine it by adding field stops and mirrors.
For example, highlight this TypeScript interface:
interface UserProfile {
id: string;
name: string;
email: string;
}
Press C-c & C-s, name it interface with key iface. The snippet buffer opens with that code. Edit it to:
# -*- mode: snippet -*-
# name: TypeScript interface
# key: iface
# --
interface ${1:InterfaceName} {
${2:id}: ${3:string};
$0
}
Save with C-c C-c. Now iface + Tab produces a customizable interface skeleton.
Integrating Snippets with Completion Frameworks
Modern Emacs setups often use company-mode or corfu for in-buffer completion. YASnippet integrates seamlessly:
- company-yasnippet — Adds snippet triggers to the Company completion popup. Install
company-yasnippetand add it tocompany-backends:
(use-package company-yasnippet
:ensure t
:config
(add-to-list 'company-backends 'company-yasnippet))
Now when you type part of a trigger, the completion menu shows matching snippets with a distinct icon, letting you expand them with company-complete.
- Consult + YASnippet — The
consultpackage can browse snippets viaconsult-yasnippet, giving you a fuzzy-searchable list of all available snippets across all modes.
Advanced Patterns
Snippet Nesting
You can expand a snippet inside another snippet's field. While editing field $3, type another trigger and press Tab — YASnippet nests the new snippet's fields inside the current one. Pressing C-d (bound to yas-skip-and-clear) exits nested fields and returns to the parent snippet.
# Parent snippet: React component with nested useState
# --
const ${1:Component} = () => {
const [${2:state}, set${2:$(capitalize yas-text)}] = us$3
return <div>$0</div>;
};
When you reach $3, typing eState (trigger for useState snippet) and pressing Tab expands the nested hook inline.
Auto-Inserting Snippets on New File Creation
Combine YASnippet with Emacs' auto-insert-mode to populate new files with snippet content automatically:
(use-package auto-insert
:config
(setq auto-insert-alist
'(("\\.tsx\\'" . "React component template")
("\\.py\\'" . "Python module template")))
(auto-insert-mode 1))
Define the templates as regular snippets and invoke them from auto-insert using yas-expand-snippet in the hook.
Snippet-Based Code Generation with Elisp
For complex generation, write a function that calls yas-expand-snippet programmatically:
(defun insert-crud-endpoints (resource-name)
"Insert Express.js CRUD routes for RESOURCE-NAME."
(interactive "sResource name: ")
(let ((plural (concat resource-name "s")))
(yas-expand-snippet
(format "
const express = require('express');
const router = express.Router();
const %s = require('../models/%s');
router.get('/%s', async (req, res) => {
const items = await %s.find();
res.json(items);
});
router.post('/%s', async (req, res) => {
const item = new %s(req.body);
await item.save();
res.status(201).json(item);
});
router.delete('/%s/:id', async (req, res) => {
await %s.findByIdAndDelete(req.params.id);
res.sendStatus(204);
});
module.exports = router;"
resource-name resource-name
plural resource-name
plural resource-name
plural resource-name)
(point) nil nil nil)))
Bind this to a key or call it interactively — it inserts a complete CRUD router customized for the resource name you provide.
Debugging Snippets
When a snippet doesn't behave as expected, use these diagnostic tools:
M-x yas-describe-tables— Lists all active snippet tables and their triggers. Check if your snippet is loaded and which mode directory it resides in.M-x yas-tryout-snippet— Opens a buffer where you can test a snippet interactively, seeing field-by-field expansion.- Check the message buffer — YASnippet logs parsing errors there. Look for "YASnippet error" lines when loading snippets.
yas-debug-snippet— Temporarily enables verbose logging for a specific snippet expansion, showing each evaluation step.
Common Pitfalls
- Tab conflict — If
Tabdoesn't expand snippets, another minor mode (likecompany-mode'scompany-select-next) may be shadowing it. Checkyas-minor-mode-mapand rebind if necessary. - Wrong mode directory — A snippet saved in
python-mode/won't activate inpython-ts-modeunless you add an alias:(push '(python-ts-mode . python-mode) yas-alias-to-buffer-version). - Unescaped characters — Dollar signs and backslashes in snippet bodies must be escaped:
\$for a literal dollar,\\for a literal backslash. - Mirror evaluation order — Mirrors evaluate when you leave a field, not when you enter it. If mirror logic depends on the current field value, use
yas-field-valueinside the mirror expression.
Best Practices
- Keep snippets atomic — One snippet, one concept. A snippet for a
try/catchblock shouldn't also generate logging and metrics instrumentation. Compose snippets via nesting instead. - Use meaningful trigger names — Prefer short but unambiguous triggers.
rfcis good;rcis too vague. If conflicts arise, use the# key:override. - Provide defaults for every field — Even if it's just
${1:name}. Defaults serve as documentation and prevent empty-field errors in generated code. - Set
$0exit point — Always define where the cursor should land after the final field. Without$0, the cursor stays at the last defined field, which might not be the most useful position. - Version-control your snippet directory — Treat
~/.emacs.d/snippets(or wherever you store custom snippets) like any other codebase. Commit it, review changes, and share it across machines. - Test snippets in isolation — Use
yas-tryout-snippetbefore relying on a snippet in production code. Verify field ordering, mirror behavior, and nested expansion. - Document non-obvious snippets — Add a
# name:line with a human-readable description. Snippets for esoteric patterns benefit from a brief comment explaining intent. - Leverage
yas-indent-line— Set# expand-env: ((yas-indent-line 'fixed))if your snippet contains multi-line blocks that must respect the surrounding indentation. - Clean up unused snippets — Periodically review your snippet tables. Stale snippets create noise and slow down trigger matching.
Beyond YASnippet: Built-in Alternatives
Emacs ships with several snippet-like facilities that work without external packages:
Abbrev (Abbreviations)
Abbrevs are the simplest form of expansion. Define them with define-abbrev or interactively with M-x add-abbrev:
(define-abbrev-table 'python-mode-abbrev-table
'(
("ifmain" "" "if __name__ == '__main__':" nil :system t)
("ipdb" "" "import pdb; pdb.set_trace()" nil :system t)
))
Abbrevs expand automatically when you type a trigger followed by a non-word character (space, punctuation) — no explicit Tab required. However, they lack field stops, mirrors, and conditional expansion.
Tempo / Skeleton
The tempo.el library (and its newer incarnation skeleton.el) provides programmable insertion with field navigation similar to YASnippet but with a Lisp-centric API:
(define-skeleton php-foreach-skeleton
"Insert a PHP foreach loop."
"Variable name: "
"foreach ($" str " as $key => $value) {" \n
> _ \n
"}" \n)
Call it with M-x php-foreach-skeleton. The _ marks the cursor position after insertion. Skeletons are useful for simple constructs but become unwieldy for complex templates with multiple interdependent fields.
Sharing Snippets Across Teams
For team consistency, distribute snippets via a shared Git repository. Add it to yas-snippet-dirs and use yas-load-directory to load it on demand:
(defun load-team-snippets ()
(interactive)
(let ((dir (expand-file-name "snippets" "~/projects/shared-config")))
(when (file-directory-p dir)
(add-to-list 'yas-snippet-dirs dir t)
(yas-load-directory dir))))
Pair this with .dir-locals.el in the project root to automatically activate team snippets when visiting project files:
((nil . ((eval . (progn
(require 'yasnippet)
(yas-load-directory
(expand-file-name ".snippets"
(locate-dominating-file
default-directory
".snippets"))))))))
This ensures every developer on the project gets the same snippet expansions without manual configuration.
Conclusion
Emacs code snippets, powered primarily by YASnippet, transform the editor from a text manipulator into an intent-driven code generation environment. By investing time in crafting well-structured snippets — complete with field stops, mirrors, conditions, and sensible defaults — you build a personalized acceleration layer that grows with your coding habits. Start with a few high-value snippets for repetitive patterns in your daily language, iterate on them using the diagnostic tools described above, and gradually expand your library. The combination of immediate keystroke savings and long-term consistency gains makes snippet mastery one of the highest-leverage skills in the Emacs developer toolkit.