SilverBullet 2.10: Enter the MultiSpace
For years, people have asked “how do I create multiple spaces with SilverBullet?” and the answer has always been: well, just deploy multiple instances, one for each.
It worked, but it never has been the smoothest experience. 2.10 finally attempts to address that by adding multi-space support from a single instance. And while at it, also adds multi-account support. Because you know, why not. And while at that, 2.10 adds a setup wizard. Wizards are cool. Everybody likes wizards.
Kidding aside, I believe this will be a win from the user experience side: no more (or at least less) messing about with environment variables to set this up!
Before we get to some of the specifics, a few words on the development phase I’m in at the moment. I’m (trying) to put the brakes on introducing huge, rampant changes to SilverBullet fundamentals at this point. I think what’s there is prety solid and what it needs most now is tuning and polish. Introducing multi-space and multi-account support I categorize as polish. It changes nothing in the main editor experience, it’s just polish around the edges.
Enter the MultiSpace
In 2.10, when installing SilverBullet fresh (so no existing files in your space folder) will activate “setup mode.” Opening up SilverBullet on the web will greet you with a setup that consists of two steps:
- Admin account setup: creates an initial account based on the username and password of your choice.
- Space setup: creates an initial space and its configuration (defaults to using URL prefix such as
/notesnow, but you can use the bare/as well).
After this, you’re all set up. SilverBullet will switch off setup mode, and you can login to your newly created space and live happily ever after.
Early expectation-setting note: even though SilverBullet now has support for multiple accounts, there is no still no real-time collaboration support. As before, if two clients (same account or not) edit the same page simultaneously, you will still get sync conflicts. Nothing changed here. Maybe at some point this will be addressed, but not today.
So yes — SilverBullet now has the basics of an account system, and you no longer configure authentication with a SB_USER environment variable containing a plain-text password. You now have the ability to create multiple user accounts, multiple spaces, and decide what account has access to what space. If you want to setup SilverBullet for your family, friends or colleagues (as you should), you now can do this more easily.
After initial setup, behavior differs slightly depending on how you set it up. If you opted to bind your new space to / you will need to visit /.spaces manually to manage your spaces and users going forward. If you opted to use URL prefixes, navigating to the root of your server will now show you a list of available spaces (and if you are an admin, the ability to create new ones, edit, remove etc.).
Compatibility note: If you’re a current SilverBullet user, your instance should keep running exactly as it has after an upgrade. 2.10 will detect that your data folder is a SilverBullet space itself (rather than a multi-space setup), and will configure itself into “single space” mode. All environment-variable based options (should) keep working as they have.
However, multi-space support is not the only new thing in 2.10.
Gettin’ baked
As previously announced in the docs as code post, 2.10 introduces Baked Sections. I’d refer to this as a “compromise feature” for those who want to start leveraging SilverBullet(+) in their companies, but are not ready to go all-in just yet, and want to give non-SB users markdown that does not rely on, specifically SB’s Lua expressions, for markdown content.
Baked sections (ab)use HTML comment syntax to inline the results of a Space Lua expression in the document.
An example. Rather than writing this:
${query[[from f = tags.feature where f.tag == "page" select f.name]]}
in your page, which would look odd when viewed e.g. in Github, Gitlab or Obsidian, you “bake” the section (hovering over the Live Preview of this expression will show a “Bake” button), which turns it into the following:
<!--#lua query[[from f = tags.feature where f.tag == "page" select f.name]] -->
| name |
| ---- |
| Baked Sections |
| Publishing |
<!--/lua-->
The <!--#lua ... --> and <!--/lua--> markers set boundaries within which SilverBullet can re-render the results of the expression. The content is updated whenever the Baked Sections: Update (Cmd-Shift-B/Ctrl-Shift-B) command is run. Since most tools will simply hide HTML-comments, this will lead to clean markdown previews in e.g. Github and Gitlab or other markdown based tools that are not SilverBullet.
The true old skool retro SilverBullet users from years back will remember this is pretty close to how query rendering started out back in the good ol’ day. As Shirley Bassey said, it’s all a little bit of history repeating.
Rust server/CLI rewrite
To simplify the tech stack, the backend and CLI, previously written in Go, have now been rewritten to Rust (SilverBullet+’s backend was already written with Rust). More background on this move is here.
SilverBullet.md changelog
A summary of changes in this release:
- Space Manager: multi-space hosting with multiple accounts is here. A fresh install pointed at an empty folder opens a browser-based first-run setup wizard that creates an admin account and your first space, then serves it in place with no restart. One server can host any number of spaces, each bound to a URL prefix or hostname.
- Baked Sections: bake
${...}Lua expressions and widgets into HTML-comment-delimited markdown (<!--#lua EXPR -->…<!--/lua-->). - Space Lua: code complete now shows documentation (where available), all available via
spacelua.*reflection APIs. - Backend and CLI have been ported to Rust (see background on this), both should be behavior preserving (that is: you shouldn’t really notice):
- The server backend (previously written in Go) has now been replaced by an adapted version of SilverBullet+‘s backend written in Rust, more unifying those code bases.
- CLI client also reimplemented/backported to Rust as well.
- This means the project is now all TypeScript + Rust.
- The goal is to make do this without regressions, but watch for any issues.
- Frontmatter in the editor now has folding: by default long frontmatter blocks fold automatically, and
frontmatterFoldingoptions let you disable auto-folding, always fold frontmatter, or change the line threshold. A subtle right-side marker folds or unfolds the whole block, and folded frontmatter previews anytagsvalue as tag chips. - Pulling my “this was experimental” card for the CLI: I removed the
sb getcommand and the/.runtime/objects/*REST API, including their dedicated client-side query bridge. Usesb query,sb evalinstead. This added too much complexity and effectively introduced another query language. - Page Picker: type
$to switch to anchor-based navigation - Fix:
SB_SHELL_BACKEND=localdisabled shell command execution instead of enabling it. - Fix: a
range(orpos) attribute in your own content could overwrite the source offsets an indexed object uses to point back at its place in the page. - Fix: only the first entry of a frontmatter list of wiki links formed a relation (e.g. an
authors:list of[[...]]items), so every entry after the first was missing from Object Graph. - Fix: major typing/navigation slowdown on pages with many internal links in large spaces.
- Fix: the service worker precached client assets through the browser’s HTTP cache, so a stale client could be copied into its cache and then served as though it were the current build leaving a persistent “A new version of SilverBullet client is available.” This should now be fixed.
- The server now sets
Cache-Controlon client assets, which matters if you run SilverBullet behind a CDN. - Lua: Space Lua comments are now properly parsed rather than stripped, and retained in the AST (part of the enabler for code complete with documentation).
- Fix: first load of an authenticated space no longer shows a spurious “Could not process config and no cached copy” alert before redirecting to the login page.
- Fix: frontmatter link live preview now follows the editor’s regular markdown preview behavior: raw YAML syntax stays visible when markdown syntax rendering is enabled, and only the link currently being edited is revealed in clean mode.
- Fix: write-mode commands (those requiring read-write, e.g. the baking commands) are now hidden in the command palette and their keybindings disabled on per-page read-only pages (
perm: ro), not just in fully read-only spaces. - Runtime API: the embedded headless-Chrome runtime now logs its lifecycle (when it launches on first use, when it becomes ready, and on crash/restart), and forwards the headless page‘s
console.*output to the server log by default. - New
codeWidget.*Lua API: register a renderer for a fenced code block language from Lua (e.g.```mermaid), previously only possible with plugs. Arender(body)function receives the code block contents and returns a widget (or markdown/HTML). - HTML comments (both inline
<!-- ... -->and block comments, including the baked-section<!--#lua … -->/<!--/lua-->markers) now render in a subtle gray and slightly smaller font in the editor, like code comments. - Fix: Space Lua now correctly truncates a parenthesized expression to a single value (Lua 5.4 semantics). Previously
(string.gsub(...))and other parenthesized multi-return calls leaked their extra return values intoreturn, call-argument, and assignment positions (e.g.table.insert(t, (string.gsub(...)))inserted two elements). Parentheses now yield exactly one value. - Fix: Lua pattern matching lost capture groups, because
ipairsdroppednilvalues from the result table (by henrikx). - Fix:
lintObjectsthrew when a page’spageMetawas undefined (by josh-j). - Fix:
mq.pollmaterialized the entire queue on every poll (by josh-j). - Fix: on Safari/WebKit, the first keystroke right after a paste could be inserted at the wrong position (e.g. pasting a URL inside
[text]()and then pressing)produced[text]()url)instead of typing over the closing bracket). WebKit left the typing caret at the pre-paste position; the editor now re-syncs it after a paste. - Navigating to a page via a link now always opens it fresh (at the top, or at an explicit
#header/@pospointer in the link) instead of restoring your previous cursor and scroll position. Returning to a page via browser Back/Forward or the Page Picker still restores where you were. Plugs/Lua can opt into restoring with the neweditor.opensyscall. - Fix: modals now set
box-sizing, so their padding no longer pushes content past the intended width (by Federico Scodelaro). - Favicon definitions cleaned up and documented following current best practices (by Jorge Marin).
- The server now compresses
GETresponses, reducing transfer sizes over slow connections.
SilverBullet+ changelog
Everything (relevant) in SilverBullet.md ships with SB+ as well, in addition to these SB+ specific updates:
- Built-in “SilverBullet Help” space bundling the silverbullet.md manual for offline viewing.
- Experimental Pro feature: integration with SilverBullet 2.10 servers configured with multiple spaces: configure your SB server under the “Pro” tab once, and switch on sync for each of your spaces with just a few clicks (no need to repeat the URL, auth info every time).
- Experimental MCP server (disabled by default, enable via the “Advanced” tab in the dashboard)
- Dashboard restyled onto SilverBullet’s shared UI components for visual consistency.
- Fix: Cmd/Ctrl-A again selects all text in native input fields (e.g. the page-picker and command-palette filters).
- Fix: Cmd/Ctrl-clicking a page link (in the editor or in query/template results) now opens the page in a new native window.
- Fix (Linux): right-clicking a link and selecting “Open Link in New Window” from the WebKitGTK context menu now opens a new SilverBullet window for the target page instead of silently doing nothing.
- Linux: the bundled WebKitGTK rendering workarounds (
WEBKIT_DISABLE_DMABUF_RENDERER,WEBKIT_DISABLE_COMPOSITING_MODE) can now be overridden at runtime. Setting either to0re-enables the corresponding GPU path. If you’ve experience poor rendering performance on Linux, try setting both these to0and see if that improves things.
Installing
- SilverBullet.md: Install instructions
- SilverBullet+: Download
Upgrading
- SilverBullet.md:
- For docker-based installs, pull the new image, stop the container and start a new one. For binary-based installs run
silverbullet --upgradeand restart. - Your open tabs should automatically indicate a reload (or two) is required to update to the latest version.
- For docker-based installs, pull the new image, stop the container and start a new one. For binary-based installs run
- SilverBullet+:
- You should automatically be notified about an available upgrade, simply click the “Upgrade” button to install it and restart.
Support SilverBullet development
Last year I made the decision to reduce my regular day job to fewer days to create more time to work on SilverBullet. This allows for deeper focus and more ambitious work. If you like this direction, and would like to support it, there are multiple ways to make this more financially palatable for me:
A big ❤️ to all already supporting this way — it’s much appreciated!
Subscribe to No SilverBullet
Get new posts by email. No spam, unsubscribe anytime.
Prefer a feed? Grab the RSS feed.