# Hjem: managing your home directory with Nix

> Hjem is an MPL 2.0 licensed Nix module and command line tool that manages the files in your home directory from a manifest, with generations you can roll back and a per user interface for people who administer more than one account. The judgement is that it deliberately refuses to manage your environment variables, and that the generation model is what makes a home directory manager safe to run.

**feel-co/hjem** — Streamlined, elegant $HOME management with Nix

- Repository: https://github.com/feel-co/hjem
- Website: https://hjem.feel-co.org/
- Stars: 693 · Forks: 27
- Language: Nix
- License: MPL-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/feel-co-hjem

## A home directory manager that starts from a manifest

Hjem, which the readme says is home in Danish, is a module system that implements a simple and streamlined way to manage files in your $HOME, such as but not limited to the files in your ~/.config. The aim is to approach the problem as an alternative, easy to grasp utility for managing your $HOME purely and safely.

The five features are listed with the framing that the project has learned from the mistakes made in the ecosystem. There is the $HOME management functionality itself and the potential for more. The codebase is small and simple with minimal abstraction. The file handling is safe and manifest based, which it delegates to another tool from the same organisation, smfh. It is multi-user by design and works with any number of users. And it is designed for ease of extensibility and integration.

The line under the list is the project's tone in a single sentence, no compromises, only comfort.

The documentation lives on a separate site, hjem.feel-co.org, and the readme is a summary of it. The licence is MPL 2.0, the project is written in Nix, the home page is the documentation site, the last push was on 2026-10-19, and there are no GitHub releases, which is normal for a Nix project since the flake is the artefact.

## Three sources for the manifest, and only one at a time

Hjem ships a standalone CLI called hjem for non-NixOS and mixed setups, and the standalone entrypoint evaluates a Hjem manifest and applies it directly to the current user.

The switch and build commands accept exactly one manifest source, and there are three of them. The first is a manifest that has already been generated. The second is a hjem.nix file, which is the configuration you write. The third is a flake output.

The readme gives an example of each shape:

```sh
$ hjem standalone switch --config ./hjem.nix
$ hjem standalone switch --flake .
$ hjem standalone switch \
  --flake . \
  --flake-attr 'hjemConfigurations."alice@laptop".manifest'
```

The last one is the interesting case, because the flake output has a conventional attribute path, with the user name and the configuration name, and the command lets you point at a different one, so the same flake can hold a manifest for a laptop and a manifest for a desktop.

A hjem.nix file may evaluate to either a manifest directly or to an attribute set with a manifest attribute, and the difference matters for the flake case, where the output is a set rather than the manifest itself. The manifest itself is a version number and a list of files:

```nix
{
  version = 3;
  files = [
    {
      type = "symlink";
      source = ./dotfiles/example;
      target = "/home/alice/.config/example";
    }
  ];
}
```

The version field is there because a manifest format that changes needs a way for the tool to know what it is looking at.

## Generations are what make it safe to experiment

The standalone lifecycle commands are the part of the design that answers the obvious objection to a home directory manager, which is that a mistake leaves your dotfiles broken.

Hjem keeps generations, and the commands are all about them. The build command evaluates and validates the manifest without applying it, so you can check a configuration before it touches your home directory. The generations command lists the stored ones. The rollback command goes back to the previous generation, and it can also take a specific generation id, which has the shape of a timestamp and a counter. The expire-generations command removes old generations by a timestamp such as thirty days, or keeps only the newest few with a keep-last option, and the remove-generations command removes explicit ids.

The readme is careful about one rule in all of these. The current generation cannot be removed, whether it is being expired by age, by count or by id, and the remove command says so directly.

The state lives in a directory under the XDG state home, $XDG_STATE_HOME/hjem/standalone, or in ~/.local/state/hjem/standalone when that variable is not set, and there is a state directory flag for the cases where you want it elsewhere.

So the workflow is: build to check, switch to apply, and if the result is wrong, roll back. The alternative, a manager that applies directly and leaves you to fix it by hand, is the one this generation model is designed to avoid.

## The module interface manages one home per user

The module side is where Hjem does its multi-tenant work, and the readme's example is a NixOS configuration with one entry per user.

Each user under hjem.users is a person whose home directory is managed. The example sets a user as enabled and then declares files in three different ways. A text file is written directly, with the name as the key and the contents as the text. A file can also be produced by a writer, in the example a file written with the Nix package writeText, which is how you get a file whose contents come from the build rather than from a source file. And a file can be produced by a generator, which transforms a Nix value into the format a program expects, with the example using the JSON generator from the standard library, and the readme notes that it works with the format helpers as well.

There is also a path for the XDG configuration directories, where a file is declared under xdg.config.files with a generator, a value and a clobber setting, and the clobber flag is what allows the tool to overwrite an existing file that it does not manage.

The note at the end of the section is the safety mechanism. Each attribute under hjem.users represents a user managed through the users.users mechanism in NixOS, and if a user does not exist, Hjem refuses to manage their $HOME by filtering the non-existent users out at file creation time. So a configuration that mentions a user who has not been created yet does not create a home directory for them by accident.

The readme is also clear that this is not a collection of modules like Home Manager. The interface is conceptually similar to that prior art, but the implementation is deliberately minimal and leaves the application specific abstractions to the user, and it notes that a module collection already exists in another repository.

## What a declared file actually becomes

The readme shows a live evaluation of a declared file, and the output is the clearest description of the model.

```sh
$ nix eval .#nixosConfigurations.test.config.hjem.users.alice.files.'".foo"' --json | jq
{
  "clobber": false,
  "enable": true,
  "executable": false,
  "generator": null,
  "relativeTo": "/home/alice",
  "source": "/nix/store/22yfhzhk0w5mgaq6c943vimsg6qlr1sh-foo",
  "target": "/home/alice/.foo",
  "text": "bar",
  "value": null
}
```

The file that was declared as a piece of text has become a store path, a source, and a target, with the text still attached. The relativeTo field says the file is relative to the home directory rather than to an absolute path, and the clobber and executable flags are the two permissions the declaration can carry.

The important structural detail is that the source is a store path, which means the content of a managed file is a Nix store object like any other, so it is reproducible and inspectable with the usual tooling. The target is the path in the home directory where the user will see it, and the two are joined by the tool rather than by a symlink you create yourself.

That is the whole difference from a dotfiles repository with a script, and it is why the file handling can be described as atomic and reliable, because the store path is already there and only the final link has to be made.

## smfh and systemd do the actual linking

The linking implementation is described in a short section, and the two names in it are the interesting part.

Hjem relies on a tool called smfh, which the readme describes as a home-baked tool, an atomic and reliable file creation tool designed by Gerg-l, and it is developed in the same organisation as Hjem. Hjem uses smfh together with systemd services to link the files into place correctly.

The footnote explains why systemd rather than activation scripts. The footnote says that systemd services are preferable to hacky activation scripts that may or may not break, and that systemd services allow for ordered dependency management across all services and easy monitoring of the Hjem related services from the central systemctl interface.

That is the whole argument for using the init system, stated in two lines. An activation script runs at some point in the boot and is a sequence of commands whose ordering and failure behaviour you have to reason about. A systemd unit declares a dependency, and the init system decides when it runs, and the monitoring of it is the same as for any other service on the machine.

The organisation of the repository follows the same separation. The cli directory holds the standalone command, the manifest directory the format, the modules directory the NixOS module, internal and lib.nix the shared Nix code, npins a pin set for the dependencies, and tests, docs and a flake for the packaging, with an .envrc for a development shell.

## Environment variables are deliberately left alone

The last thing the readme explains is the thing it does not do, and the way it is written is refreshing.

Hjem does not manage user environments as one might expect. Instead it provides a convenient environment.sessionVariables option that you can use to store your variables, and the script this produces is a POSIX-compliant script generated by Hjem which you can source in your shell configurations.

So the answer to the question every home directory manager gets asked is that it does not touch your shell environment, and if you want variables in it, it will write a script for you and you decide where to source it from. The difference matters because a tool that edits your shell profile is a tool that can break your login, and one that writes a file you source yourself keeps the last word with you.

The readme also says that usage without flakes is supported, with a table mapping the flake commands to the shell commands for people who do not use flakes, and the documentation site carries the rest.

Taken together, the design is consistent. The tool manages files, the tool keeps generations, the tool leaves the environment alone, and the tool refuses to manage a home directory for a user who does not exist. Each of those is a decision about what can go wrong, which is a better foundation for a tool that touches your home directory than a long list of features would be.

## Conclusion

Hjem fits a Nix user who wants their configuration directory treated as a managed resource rather than as a pile of dotfiles, because the files are declared in a manifest, the linking is atomic, and every change is a generation you can roll back. It is the wrong choice if you expect it to manage your shell environment, because the readme is explicit that it does not and offers only a session variables option that writes a script for you to source. It is also the wrong choice if you are not already using Nix, since the standalone mode still expects a Nix expression or a flake to evaluate. Before you adopt it, read the generation commands rather than the file syntax, because the rollback and expiry behaviour is what decides whether you can experiment with a dotfiles change on a machine you rely on.

## FAQ

### What is Hjem?

It is an MPL 2.0 licensed Nix module system and standalone CLI for managing the files in your home directory from a manifest. The name is Danish for home, and the project provides multi-user support, manifest based file handling with atomic linking, and generations you can roll back.

### How do I apply a configuration with the standalone CLI?

Run hjem standalone switch with exactly one source, either --config for a hjem.nix file, --flake for a flake output, or --manifest for an already generated manifest. The build command evaluates and validates without applying, and the state is kept under the XDG state home.

### Can I roll back a change to my home directory?

Yes. Hjem stores generations, and the rollback command returns to the previous one or to a specific generation id, while expire-generations removes old ones by age or by count. The current generation cannot be removed.

### Does Hjem manage my environment variables?

No. The readme says explicitly that it does not manage user environments as one might expect, and offers an environment.sessionVariables option that stores the variables in a POSIX-compliant script you source from your shell configuration.

## Sources

- [feel-co/hjem on GitHub](https://github.com/feel-co/hjem)
- [Issues](https://github.com/feel-co/hjem/issues)
- [License: MPL-2.0](https://github.com/feel-co/hjem/blob/main/LICENSE)
- [Project website](https://hjem.feel-co.org/)
- [README](https://github.com/feel-co/hjem/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/feel-co-hjem
