A look at the SilverBullet note-taking application
SilverBullet is a MIT-licensed note-taking application, designed to run as a self-hosted web server. Started in 2022, the project is approaching its 2.0 release, making this a good time to explore the features it offers. SilverBullet stores notes as plain Markdown files, and provides a Lua scripting API to customize the application's appearance and behavior.
Architecture
SilverBullet features an unusual design; despite being a single-user application, it is written as a self-hosted web app. The project is distributed as a standalone server executable. The web-based interface that it provides is a progressive web app (PWA), which lets devices that support PWAs, such as mobile phones, optionally "install" the application as though it were a native executable. For all other devices, it can be accessed through the browser like a normal website. The server is responsible for reading and writing files from disk, and synchronizing changes between multiple clients. When a new client connects (by opening the web interface), it furnishes the client with a full copy of the user's notes.
That copy is kept in the client's local storage; while browsers do limit this space, it's still more than large enough for a set of plain-text files. The rendering, editing, indexing, and even Lua scripting of the files is performed by the client code running in the browser. This lets the user view and edit their files even when offline; when they reconnect, the changes are automatically pushed back to the server. During my testing, I did notice that the synchronization protocol is fairly chatty. Rather than using push notifications or WebSockets connections, SilverBullet directly polls the server every few seconds. This is not a problem, per se, but some users may find it inelegant.
While SilverBullet does allow specifying a username and password that must be given to access the application, the intended deployment scenario seems to be putting it on one's local computer, a Raspberry Pi in one's home, or on an existing private network, so that only trusted devices can connect to it. SilverBullet does not support multiple users or editing permissions, but it does support running in a read-only mode, to allow publishing static versions of a set of notes.
Both SilverBullet's client and server are written in TypeScript, although there is also a substantial Lua library that implements SilverBullet's database and styling features. The server program is a self-contained bundle of all the components, so users who don't wish to compile the project from scratch can just download the file from the project's GitHub repository and run it. Running the project for the first time creates an index.md file with some tips to get started:
Editing
The core of any note-taking application is the editing experience. SilverBullet's editing is an interesting hybrid between "what you see is what you get" (WYSIWYG) and editing plain Markdown. Specifically, individual Markdown files are rendered according to the CommonMark standard, a unified version of Markdown supported by several tools. The rendered form appears on the page with its full rich formatting. In fact, on first looking at my new installation, I did not realize that the text (pictured above) was actually directly editable. Placing the cursor anywhere on the page reveals the truth: the underlying document can be edited right there, with no separate editing mode. Moving the cursor inside a Markdown element (such as a **bold** phrase) reveals the markup used to create it, so that one can edit it. The usual Control-b (bold) and Control-i (italic) shortcuts are available as well.
Vim users will find that SilverBullet supports optional Vim-style keybindings. Although I myself am an unrepentant Emacs user, the Vim keybindings seemed accurate enough to me that I believe muscle memory should transfer over.
Another feature borrowed from existing editors is the ability to run commands, which can be defined in Lua, bound to keys, or invoked by name from a searchable list. SilverBullet takes an interesting approach to configuring and customizing the application; Lua code can be directly embedded in a file as a Markdown code block, and either referenced elsewhere, or run directly and spliced into the rendered page. This lets the user create interactive elements in their notes. The application's configuration lives in CONFIG.md; any Lua code in that file is run when the SilverBullet client starts, to set everything up. Anything that can't be handled using the Lua API can be done with the TypeScript plug-in API, which unfortunately doesn't work in the same manner, requiring one to write separate TypeScript files.
Tags and searching
SilverBullet supports tagging many parts of Markdown files, such as tasks, code blocks, bullet-list items, and so on. Writing #tagname in a document will attach the tag to whatever Markdown element it appears after or within. Parts of files are also automatically marked with certain default tags, such as what type of Markdown element they are and what page they appear on. This is, so far, not much different from any note-taking application. Where SilverBullet differs is that it makes a searchable index of these tags available not only to the user, but also to the Lua code fragments embedded in their documents.
The application provides a custom SQL-like query language for interacting with the tag database. SilverBullet's documentation shares this example of how that can be used to create interactive, always up-to-date tables of tasks, pages, or other items meeting a given criterion:
${template.each(query[[
from index.tag "page"
order by _.lastModified desc
limit 5
]], templates.pageItem)}
Readers familiar with Lua will recognize the [[...]] multi-line string syntax, here being used to give a string to the query() function. This snippet searches for the five most recently modified pages, renders them using the templates.pageItem template (which renders an appropriately titled link to each page), and then embeds the result in the current page using the special ${...} syntax.
The tag database also powers several of SilverBullet's internal features. For example, the default style for a page includes a snippet at the bottom that finds all of the places in the user's documents that link to that page, and displays a back-link with a little bit of context. The project's community forums also contain a discussion board for sharing useful bits of Lua code.
Future prospects
SilverBullet has, for the past several months, been undergoing a rewrite from version 1 to version 2. At the time of writing, the project has published four pre-releases for 2.0.0, and seems likely to stabilize things quite soon. Some features from version 1, such as "online mode" and non-Lua-based interactive queries, were removed because they made it difficult to extend and support the application. Zef Hemel, the founder and primary developer of the project, explained the move like this:
SilverBullet has grown into a large project over the last three years. About half a year ago I started working on Lua integration, and this [made] me discover a significantly more elegant way to implement a bunch of features in SilverBullet than all the custom features I had built before.
Initially, I implemented this "alternative universe" style, but lately this has become a significant burden for me to mentally handle and maintain both these universes long term. Therefore, I contemplated taking a more radical approach and introducing a "v2" where I would do some serious rapture-style removal of features to simplify the product, but also keep it more sustainable by — well — primarily me (although I do get more and more outside contributions, which is great).
Outside contributions to the project are welcome but currently somewhat infrequent. Documents from version 1 are viewable and editable in version 2, since they're both just Markdown. But the documentation does have a guide on converting old queries and interactive elements to use the new Lua API.
Conclusion
While it will be hard for any note-taking program to tempt me away from Emacs's org-mode, I do think SilverBullet is a compelling alternative. Its design as an offline-first web app backed by plain files promises to offer the best of both worlds. Plain files can be imported, exported, and backed up trivially; but making the primary interface web-based allows for easy styling and synchronization across devices. The integration of Lua scripts directly into the user's documents is a flexible platform on which to customize a potentially personal platform.
While the 2.0.0 release is not quite ready, the pre-release that I tested seemed stable. People who are happy with their current note-taking solution are unlikely to want to switch, but those looking to try a simple, flexible alternative may well want to give SilverBullet a look.
