# How twist.nix Fetches Archives Directly from ELPA URLs: A Technical Deep Dive

> Discover how twist.nix fetches archives directly from ELPA URLs. Learn its technical process of parsing indexes, creating flake references, and downloading package files.

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

---

**twist.nix fetches Emacs packages directly from ELPA URLs by parsing the `archive-contents` index into Nix attribute sets, converting entries into Flake-compatible references via `flakeRefAttrsFromElpaAttrs`, and downloading tarballs or single `.el` files using `fetchTree`.**

twist.nix is a Nix-based Emacs package manager that eliminates the need for pre-generated lock files by fetching packages directly from upstream sources. When working with GNU ELPA or MELPA archives, twist.nix supports fetching archives directly from ELPA URLs through a three-stage pipeline that transforms archive metadata into reproducible Nix derivations.

## Parsing the ELPA archive-contents File

The process begins in `pkgs/build-support/elisp/readArchiveContents.nix`, which downloads the `archive-contents` file from an ELPA URL and transforms it into a structured Nix attribute set:

```nix

# pkgs/build-support/elisp/readArchiveContents.nix

{
  lib,
  fromElisp,
}: prefixUrl:
with builtins;
  lib.pipe (fetchurl (prefixUrl + "archive-contents")) [
    readFile
    fromElisp.fromElisp               # parse Lisp-style list

    head
    tail                               # drop the leading "1"

    (map (xs: { name = head xs; value = tail xs; }))
    listToAttrs
  ]

```

The `fetchurl` call constructs the full ELPA URL by appending `archive-contents` to the base `prefixUrl`. The `fromElisp.fromElisp` function parses the Lisp-style data structure into Nix values, producing an attribute set where each key is a package name and each value contains the package's metadata.

## Converting ELPA Metadata to Flake References

Once the index is parsed, `pkgs/emacs/data/inventory/elpa.nix` converts each ELPA entry into a Flake-compatible source reference using `lib.flakeRefAttrsFromElpaAttrs`:

```nix

# pkgs/emacs/data/inventory/elpa.nix (excerpt)

makeExternal = ename: entry: let
  …
in self: {
  doTangle = true;
  src =
    if hasAttr ename flakeLockData
    then fetchTree flakeLockData.${ename}
    else
      (if mode == "build" then trace else traceVerbose)
      "Impure input for package ${ename} (in elpa.nix): ${toJSON self.origin}"
      (fetchTree self.origin);
  origin = lib.flakeRefAttrsFromElpaAttrs { preferReleaseBranch = true; } entry;
  …
}

```

The `flakeRefAttrsFromElpaAttrs` function, exported from `pkgs/build-support/default.nix`, transforms ELPA entry fields (such as `url`, `lisp-dir`, and `doc`) into a structured Flake reference. This reference takes the form of a GitHub URL (`github:<owner>/<repo>/<ref>?dir=<path>`) for repository-based packages, or a direct HTTP URL (`https://elpa.gnu.org/packages/<pkg>-<ver>.tar`) for archive-based packages.

## Fetching the Actual Archive Files

The final stage occurs in `pkgs/emacs/data/inventory/archive.nix`, which constructs the exact download URL and uses `fetchTree` to retrieve the package:

```nix

# pkgs/emacs/data/inventory/archive.nix (excerpt)

src = fetchTree (builtins.removeAttrs archive ["narHash"]);
archive = {
  type = if elpaType == "tar" then "tarball" else "file";
  url = lib.concatStrings [
    url                # base ELPA URL, e.g. https://elpa.gnu.org/packages/

    ename "-" version
    (if elpaType == "tar" then ".tar" else ".el")
  ];
};

```

This logic checks the `elpaType` field to determine whether the package is distributed as a **tarball** or a **single `.el` file**. For tar archives, it sets the `fetchTree` type to `"tarball"` and appends `.tar` to the URL. For single-file packages, it uses type `"file"` and appends `.el`. Because `fetchTree` supports both formats natively (Nix 2.9+), the archive is retrieved directly from the ELPA server without requiring intermediate processing or lock files.

## Summary

- **twist.nix** parses ELPA indices using `readArchiveContents.nix` and the `fromElisp` library to convert Lisp-style `archive-contents` files into structured Nix attribute sets.
- The `flakeRefAttrsFromElpaAttrs` function in `elpa.nix` transforms package metadata into Flake-compatible references, supporting both Git repositories and direct HTTP archives.
- `archive.nix` constructs precise download URLs for `.tar` and `.el` files, using `fetchTree` to fetch them directly from ELPA URLs without intermediate lock files.
- This architecture enables reproducible builds from official ELPA sources while maintaining compatibility with both tarball and single-file package distributions.

## Frequently Asked Questions

### What is the difference between ELPA and MELPA support in twist.nix?

Both archives use the same underlying mechanism. The `archive.nix` inventory handles any ELPA-compatible archive format, whether from GNU ELPA, MELPA, or custom archives, by parsing their `archive-contents` files and constructing the appropriate download URLs.

### How does twist.nix handle single-file `.el` packages versus tarballs?

The `archive.nix` logic checks the `elpaType` field. If it equals `"tar"`, it sets `fetchTree` type to `"tarball"` and appends `.tar` to the URL. Otherwise, it uses type `"file"` and appends `.el` for single-file archives, allowing `fetchTree` to handle both formats natively.

### Can I use twist.nix with private ELPA archives?

Yes. You can point to any URL serving an `archive-contents` file in the standard format. The `readArchiveContents.nix` function accepts a custom `prefixUrl` parameter, allowing you to specify private archive endpoints or mirrors instead of the default GNU ELPA URLs.

### Why does twist.nix convert ELPA entries to Flake references instead of using fetchurl directly?

Using `flakeRefAttrsFromElpaAttrs` enables uniform handling of both Git-based and archive-based sources through `fetchTree`. This abstraction provides built-in caching, integrity checking, and support for different backend protocols (git, tarball, file) via a single interface, rather than requiring separate fetcher implementations for each source type.