← Back to DevBytes

Emacs Code Snippets: Complete Guide

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:

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

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

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:

(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.

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:

Common Pitfalls

Best Practices

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles