#notes #zettelkasten #knowledge #academia #hypertext

app zettels

Zettels is a command line tool implementing Niklas Luhmann's system of a Zettelkasten. It is designed for people who want to use their favourite text editor to edit their notes.

4 releases (2 breaking)

Uses old Rust 2015

0.3.0 Mar 12, 2022
0.2.0 Jun 8, 2020
0.1.1 Jun 4, 2020
0.1.0 Jun 3, 2020

#1075 in Command line utilities


786 lines


Zettels is a command line tool which implements Niklas Luhmann's system of a "Zettelkasten". It is designed for people who want to use their favourite text editor to edit their notes.

It uses libzettels as a backend.

If you have no idea what a Zettelkasten is, read below at "What the heck is a Zettelkasten?"

Code and crate

Zettels is written in Rust. Find code and crate here:

Zettels is still in alpha stage and probably buggy.


1. Binary packages

See the Releases page. There are binary packages available for

  • 64 bit GNU/Linux
  • 32 bit GNU/Linux
  • 64 bit Windows

Also, there are *.deb packages for Debian-based GNU/Linux distros (e.g. Ubuntu).

2. via Cargo

Rust's package manager cargo offers a really easy installation method:

cargo install zettels

3. From source

Checkout the repository or download the source code from Releases. If you choose this way, I assume you know what you're doing.

What does Zettels do?

It's intended to be used for a "Zettelkasten" like Niklas Luhmann used it. Zettels is a tool to index markdown files (your zettels). It takes a directory (your Zettelkasten's root directory) containing Markdown files (which may be in sub-directories) with a YAML metadata block (as defined by pandoc).

Zettels bundles the information about the relations between these files (your zettels) in a queryable index. To populate this index, Zettels does two things:

  1. It inspects the fields title, keywords and followups of the YAML metadata block.
title:  'Example Zettel'
keywords: [example, question]
followups: [file.md, subdir/anotherfile.md, ../yetanotherfile.md]
foo: 'Potentially more data ignored by libzettels.'
  1. It parses the inline-style links (like [example](afile.md)) of the markdown document body and extracts the targets of these links.

The resulting index contains the metadata and the targets of the hyperlinks.

Example files

If you want to look at some example files to get a better idea, zettels has a subcommand that will generate some for you:

  1. Just example zettels. Probably most useful to users, to just see how zettel files look like.
zettels examples --bare
  1. Example zettels and a config file, probably only useful for developers
zettels examples --with_config
  1. Example zettels complete with config file and an index
zettels examples --with_index

You can run that configuration by running zettels with the option -c and supplying it with the path to the generated config file.

What the heck is a Zettelkasten?

"Zettel" is German for "note" or "slip of paper". "Kasten" is German for "box". Think of old style library catalogues.

Obviously, this piece of software is not a box of paper sheets. However, Niklas Luhmann used such a box in a very specific way for his academic work.

A wonderful introduction in Luhmann's system of a Zettelkasten are the slides of a talk by Daniel Lüdecke: Introduction to Luhmann's Zettelkasten-Thinking

In Luhmann's own words: Communicating with Slip Boxes (translation of "Kommunikation mit Zettelkästen").

If you speak German, there's more:


If you're looking for a GUI, all-in-one approach to implementing Luhmann's idea into software, I can recommend Daniel Lüdecke's Zettelkasten (sjPlot/Zettelkasten).

Zettel format

Zettels doesn't require your markdown files to have a metadata block. But to be really effective parts of your Zettelkasten, a YAML metadata block containing an entry for title, keywords and followups is necessary.

title:  'Example Zettel'
keywords: [example, question]
followups: [file.md, subdir/anotherfile.md, ../yetanotherfile.md]

If no such metadata is present, Zettels will replace it with appropriate "empty" values in the index:

  • title: "untitled"
  • keywords: "[]"
  • followups: "[]"

Instead of finishing the metadata block with ... you can also use ---.

title:  'Example Zettel'
keywords: [example, question]
followups: [file.md, subdir/anotherfile.md, ../yetanotherfile.md]

In fact, a zettel file may contain several YAML-blocks. However, Zettels will only parse the first one. The metadata block may contain a variety of other entries (e.g. author, date) – maybe for other tools, like pandoc – but those other entries are ignored by Zettels and do not become part of Zettels' index.

To manually link between zettels, use the "inline syntax" for markdown hyperlinks:

[link text](url)

Links between zettel files should be relative links. The same is true for entries in followups.



  1. run zettels setup for config
  2. run zettels -h to see usage info

Search and inspect

There are two main ways of querying the Zettelkasten.

Searching is a means to find an entry into the Zettelkasten. Zettels implements searching the title and keywords fields of the zettel. Just for the sake of completeness: searching the contents of the zettel is not a functionality of zettels. For that there are specialized tools like grep oder ripgrep out there.

Search for keywords -k, --keywords:

zettels -k foo

Search for title -t, --title or -e, --exacttitle:

zettels -t foo

Both can be combined:

zettels -k foo -t bar

Results can be limited to zettels that match all queries: -a, --all

zettels -k foo -t bar -a

And of course, that applies to mor than one search term, too.

zettels -k foo bar -a

2. Inspect

Inspecting the relations between zettels is the second way of querying the zettelkasten. Inspection requires a "scope", meaning a list of zettels to inspect. This scope is either specified by the user, or it's the result of search or it's the whole zettelkasten.

For inspecting the "followup" relation, a key concept are "sequences". When a zettel designates another as a followup, they form a sequence. Further followups extend that sequence or might branch into subsequences.

Example Sequence

Show to which sequences zettels specified by SCOPE belong -s, --sequences:

zettels -s file1.md

Show all zettels of the sequences specified by SCOPE -z, --zettels:

zettels -z file1.md

Show zettels that belong to the sequence(s) specified by SCOPE, plus all ancestors of SCOPE -f, --family:

zettels -f file1.md

Show zettels that belong to the sequence(s) specified by SCOPE, plus all ancestors of SCOPE, plus all descendants of those ancestors -w, --wholefamily:

zettels -w file1.md

For inspecting the "link" relation, we can inspect SCOPE's outgoing links -l, --links:

zettels -l file1.md

or incoming links -i, --incoming:

zettels -i file1.md


As mentioned before, the result of a search can be used as SCOPE for inspection. So this will show all outgoing links of zettels that have the word "foo" in their titles:

zettels -t foo -l


~234K SLoC