A personal and opinionated Emacs framework born out of config bankruptcy.
Scratch is a small Doom inspired configuration framework with fewer and deliberately chosen packages.
What comes in the box reflects what I actually use day to day and may change at any time. You're welcome to use it as-is, fork it, or send PRs, but this is mostly a personal project and comes with no guarantees.
Core requirements:
- Emacs 30+
- Git for package management via straight.el
- A nerd font (
M-x nerd-icons-install-fontson first install) - Tree-sitter grammars (
M-x treesit-auto-install-allafter enabling a new language module) - ripgrep for project search (
brew install ripgrep) - cmake + libtool for
:term vterm(builds a native module on first load);:term ghostelauto-downloads its binary instead
Language and tooling modules shell out to external programs. Install what matches the modules you enable:
| Module(s) | Tool(s) | Install |
|---|---|---|
:lang python |
uv, ty, ruff |
brew install uv ty ruff |
:lang toml |
taplo |
brew install taplo |
:lang rust |
rustup toolchain components |
rustup component add rust-analyzer clippy |
:lang elixir |
dexter LSP server |
on $PATH (see module docs) |
:lang erlang |
ELP + erlfmt |
brew install erlang-language-platform erlfmt |
:lang javascript |
typescript-language-server (+deno) |
npm i -g typescript-language-server typescript |
:lang json / :lang yaml |
npm LSP servers | npm i -g vscode-langservers-extracted yaml-language-server |
:lang likec4 |
likec4 |
brew install likec4 (or npm i -g likec4) |
:lang csharp |
.NET SDK + global tools | dotnet tool install -g csharp-ls csharpier |
:lang markdown |
cmark-gfm |
brew install cmark-gfm |
:tools mise |
mise |
brew install mise |
:tools direnv |
direnv |
brew install direnv |
:tools just |
just |
brew install just |
:tools dape |
adapter per project (none for python) | python uses uv run --with debugpy at debug time |
Notes:
brewalternatives: most of the above also install via other channels --uv tool install ty ruff,npm i -g,dotnet tool, or the project's own toolchain. Use whatever your machine prefers; the tool just has to be on the$PATHEmacs inherits.- taplo caveat: the PyPI wheel (
uv tool install taplo,pip install taplo) ships without LSP support -- lsp-mode fails with "the LSP is not part of this build". Usebrew install taplo,cargo install taplo-cli --locked, or the GitHub release binary. - After installing tools, run
scratch envso the Emacs daemon snapshots the updated$PATH.
Clone the repo as your Emacs directory and run the bootstrap:
git clone https://github.com/rhblind/scratch-emacs.git ~/.config/emacs
~/.config/emacs/bin/scratch install
~/.config/emacs/bin/scratch syncTip
It's a good idea to add ~/.config/emacs/bin/scratch to your PATH so that it's easy to run it from anywhere.
| Command | Description |
|---|---|
scratch install |
Bootstrap ~/.scratch.d/ with a starter config.org. This is your personal config. |
scratch sync |
Install or purges packages and tangle config.org into elisp files for the framework. |
scratch upgrade |
Pull the framework, validate your config against it, and sync. |
scratch freeze |
Pin packages to ~/.scratch.d/straight-lock.el for reproducible builds. |
scratch env |
Snapshot shell environment for Emacs. Re-run after editing shell rc files. |
scratch help |
Show available commands and options. |
After adding or upgrading a package, run scratch freeze and commit
the lockfile for reproducible installs.
Modules live under modules/<category>/<name>/ in the framework.
Each module has a packages.el (package declarations) and a
config.el (configuration). No module loads unless you opt in via the
scratch! macro in your config.org:
(scratch! :editor (evil +everywhere) leader smartparens
:completion vertico corfu
:emacs (vc +forge +gutter) ibuffer dired
:checkers syntax
:tools (lsp +peek) editorconfig direnv mise dape
:lang org markdown (javascript +deno) json yaml toml (rust +clippy) (python +debug)
:llm (claude-ide +mcp +ide-diff) (eca +completion)
:term vterm
:os macos
:ui theme modeline fonts treemacs workspaces)Flags (like +everywhere, +forge) toggle optional features within a
module. Without scratch! you get a bare Emacs with only the
early-init performance knobs.
See modules/README.md for the full list and per-module documentation.
Configuration is literate: you edit a single ~/.scratch.d/config.org
file that tangles into the elisp files the framework loads. This keeps
rationale next to the code and makes scratch sync the single entry
point for applying changes. Two sections matter:
- Modules (tangled to
packages.el): thescratch!call declares which modules to load. Add/remove modules and flags here, then runscratch sync. - Configuration (tangled to
config.el): runs after all modules load. Override variables, add hooks, install extra packages.
Saving config.org in Emacs re-tangles automatically. If edited
externally, run scratch sync or Emacs will nudge on next start.
Themes default to modus-themes with +auto (follows OS appearance).
Override with scratch-theme-dark / scratch-theme-light. Leader
keys (SPC, M-SPC, ,) can be changed via scratch-leader-key,
scratch-leader-non-normal-key, and scratch-localleader-key. Set
all of these before the scratch! call.
The framework and your personal config are kept in separate directories. The framework directory is a git clone you can pull or reset without losing your settings. The user directory holds your personal config and is the one you version-control.
~/.config/emacs/
init.el framework entry point
early-init.el performance knobs
bin/scratch CLI
lisp/ framework-level elisp
modules/<category>/<name>/
packages.el package declarations
config.el configuration
~/.scratch.d/ user dir ($SCRATCHDIR)
config.org literate config (tangles to config.el + packages.el)