# How to Use twist.nix to Build Emacs Packages from Source

> Learn to build Emacs packages from source using twist.nix. This Nix library automates fetching and compiling upstream code for reproducible builds. Get started today.

- Repository: [Emacs Twist/twist.nix](https://github.com/emacs-twist/twist.nix)
- Tags: how-to-guide
- Published: 2026-03-01

---

**twist.nix is a Nix library that converts Emacs package declarations into reproducible, source-based Nix derivations by fetching upstream code and compiling it in a sandboxed environment.**

The **emacs-twist/twist.nix** repository provides a Nix-native solution for managing Emacs configurations entirely from source. Unlike traditional Emacs package managers that rely on pre-built archives, twist.nix fetches package source code directly from Git repositories and ELPA archives, then compiles them inside pure Nix derivations. This approach guarantees reproducible builds while giving you complete control over package versions and build parameters.

## twist.nix Architecture Overview

### Core Entry Point (`pkgs/emacs/default.nix`)

The library's main interface resides in `pkgs/emacs/default.nix`. This file accepts a configuration attribute set containing your registries, lock directory, init files, and overrides. It produces an attribute scope containing `elispPackages` (a map of package derivations), `emacsWrapper` (the final Emacs executable), and `generateLockDir` (a script for pinning dependencies).

The entry point orchestrates dependency resolution by recursively walking `packageRequires` headers from each package's metadata. It automatically excludes built-in Emacs libraries from the dependency graph unless explicitly included in `initialLibraries`.

### Package Builder (`pkgs/emacs/build/default.nix`)

Individual package compilation happens in `pkgs/emacs/build/default.nix`. This derivation:

- Copies source files respecting the `files` attribute map
- Runs byte-compilation via `emacs --batch -f batch-byte-compile`
- Optionally executes native compilation ahead-of-time when `nativeComp && nativeCompileAhead` is enabled
- Generates autoloads using `batch-update-autoloads`
- Installs compiled Lisp files to `$out/share/emacs/site-lisp`
- Builds Info manuals to a separate `info` output when documentation exists

### Registry Enumeration

The `pkgs/emacs/data/inventory/*.nix` modules convert abstract registry entries into concrete package definitions. The `enumerateConcretePackageSet` function processes MELPA recipes, ELPA archives, and Git-based registries to extract source locations (`src`), Lisp file lists, and metadata.

## Step-by-Step: Building Emacs Packages from Source with twist.nix

### 1. Create the Configuration Expression

Start by creating a Nix expression that imports twist.nix. The repository provides a reference implementation in `test/twist.nix`. Your configuration must specify:

- `pkgs`: The Nixpkgs instance
- `emacsPackage`: Which Emacs derivation to use (e.g., `pkgs.emacsPgtkGcc`)
- `registries`: List of package sources
- `lockDir`: Directory path for lock files
- `initFiles`: Optional list of Emacs init files to parse for package declarations

### 2. Configure Package Registries

Define `registries` as a list of attribute sets describing where to find packages. According to the source code in `pkgs/emacs/default.nix`, each registry requires a `type` field (such as `melpa`, `elpa`, `gitmodules`, or `archive-contents`) and a `path` pointing to the local checkout.

```nix
registries = [
  { type = "elpa"; path = inputs.gnu-elpa.outPath + "/elpa-packages"; }
  { name = "melpa"; type = "melpa"; path = inputs.melpa.outPath + "/recipes"; }
  { type = "gitmodules"; name = "emacsmirror"; path = inputs.epkgs.outPath + "/.gitmodules"; }
];

```

Each entry points to a Git tree supplied via flake inputs. The library reads MELPA recipes, ELPA package directories, or `.gitmodules` files to construct the package inventory.

### 3. Override Package Sources

Use `inputOverrides` to substitute upstream sources or modify package files. This attribute set accepts package names mapped to functions that transform the package configuration.

```nix
inputOverrides = {
  tramp = _: _: {
    origin = {
      type = "github";
      owner = "emacs-straight";
      repo = "tramp";
      ref = "master";
    };
  };
  bbdb = _: super: {
    files = builtins.removeAttrs super.files [ "bbdb-notmuch.el" "bbdb-vm.el" ];
  };
};

```

The first argument provides the final package set, the second provides the previous definition, allowing you to override `origin` (source) or `files` (which source files to include).

### 4. Generate Lock Files

Run the lock generation script to pin exact package revisions:

```bash
nix run .#generateLockDir

```

This executes the script produced by `default.nix` → `generateLockDir`, writing `flake.lock` (Git revisions) and `archive.lock` (archive-based packages) to your configured `lockDir`. Commit these files to version control for reproducible builds across machines.

### 5. Build the Emacs Wrapper

Compile all packages and create the final Emacs executable:

```bash
nix build .#emacsWrapper

```

The resulting derivation contains an `emacs` binary with all `elispPackages` bundled in `emacsWithPackages` style. The wrapper includes compiled Lisp files from the source repositories in its load path.

## Complete Configuration Example

Here is a minimal working configuration based on `test/twist.nix`:

```nix
{ pkgs, emacsPackage, inputs, initialLibraries ? null }:
{
  inherit pkgs;
  inherit emacsPackage;
  initFiles = [ ./init.el ];
  lockDir = ./lock;

  registries = [
    { type = "elpa"; path = inputs.gnu-elpa.outPath + "/elpa-packages"; }
    { name = "melpa"; type = "melpa"; path = inputs.melpa.outPath + "/recipes"; }
    { type = "elpa"; path = inputs.nongnu.outPath + "/elpa-packages"; }
    { name = "gnu";
      type = "archive-contents";
      path = inputs.gnu-elpa-archive.outPath;
      base-url = "https://raw.githubusercontent.com/d12frosted/elpa-mirror/master/gnu/"; }
    { name = "emacsmirror";
      type = "gitmodules";
      path = inputs.epkgs.outPath + "/.gitmodules"; }
  ];

  inputOverrides = {
    bbdb = _: super: {
      files = builtins.removeAttrs super.files [ "bbdb-notmuch.el" "bbdb-vm.el" "bbdb-vm-aux.el" ];
    };
    tramp = _: _: {
      origin = { type = "github"; owner = "emacs-straight"; repo = "tramp"; ref = "master"; };
    };
  };

  postCommandOnGeneratingLockDir = ''
    echo "Lock files generated in ${./lock}"
  '';
}

```

This configuration imports packages from GNU ELPA, MELPA, NonGNU ELPA, and Emacsmirror, while overriding specific package sources and pruning unnecessary files from the BBDB package.

## Integration with Home Manager

Deploy your twist.nix configuration via Home Manager using the `emacsWrapper` output:

```nix
{ pkgs, ... }:

let
  twist = import ./twist.nix {
    inherit pkgs;
    emacsPackage = pkgs.emacsPgtkGcc;
    lockDir = ./my-lock;
    registries = [
      { type = "melpa"; path = inputs.melpa.outPath + "/recipes"; }
      { type = "elpa"; path = inputs.gnu-elpa.outPath + "/elpa-packages"; }
    ];
    initFiles = [ ./init.el ];
  };
in {
  home.packages = [ twist.emacsWrapper ];
}

```

The `emacsWrapper` provides a ready-to-use Emacs installation that includes all compiled packages from your lock directory.

## Summary

- **twist.nix** builds Emacs packages from source using pure Nix derivations defined in `pkgs/emacs/default.nix` and `pkgs/emacs/build/default.nix`.
- The library resolves dependencies by parsing `packageRequires` headers and supports multiple registry types including MELPA, ELPA, and Git modules.
- Configuration requires defining `registries`, `lockDir`, and optionally `initFiles` and `inputOverrides`.
- The `generateLockDir` script creates reproducible lock files capturing exact package revisions.
- Final output `emacsWrapper` bundles all compiled packages into a single Emacs executable suitable for Home Manager or NixOS deployment.

## Frequently Asked Questions

### What package registries does twist.nix support?

twist.nix supports **MELPA** (`type = "melpa"`), **ELPA** (`type = "elpa"`), **Emacsmirror** via gitmodules (`type = "gitmodules"`), and archive-contents (`type = "archive-contents"`). The registry types are implemented in `pkgs/emacs/data/inventory/*.nix`, which parse recipe files or package directories to extract source locations and metadata.

### How does twist.nix handle package dependencies?

The library computes `allDependencies` by recursively walking the `packageRequires` fields derived from each package's Package-Requires header. It converts these requirements into Nix attribute references and automatically excludes built-in Emacs libraries unless explicitly listed in `initialLibraries`. The dependency graph is resolved during evaluation in `pkgs/emacs/default.nix`.

### Can I use twist.nix with native compilation?

Yes. When you provide an Emacs package with native compilation support (such as `pkgs.emacsPgtkGcc`), the builder in `pkgs/emacs/build/default.nix` checks the `nativeComp && nativeCompileAhead` condition. If true, it compiles Emacs Lisp files to native code ahead of time using the appropriate Emacs batch commands.

### Where does twist.nix install compiled Emacs Lisp files?

The builder installs compiled files to `$out/share/emacs/site-lisp` within each package's derivation. The final `emacsWrapper` aggregates these paths into the Emacs load path. Info manuals are stored in a separate `info` output when available in the package source.