Extensions
An extension adds commands, or a library of documents, to Burrow. It’s a separate program that Burrow starts and talks to. Nothing runs unless your config file names it, and an extension gets only the permissions you grant it.
Burrow comes with four extensions, all off until you turn them on: markdown-notes, cross-post, reading-time and inspect. You can also install extensions other people write, or write your own.
⇧⌘P (Post › Extensions…) opens the Extensions palette: the commands your running extensions offer on this screen, Browse for each notes folder, and Manage extensions… (what’s installed, whether it’s on, and what it’s allowed). Manage is also where you turn one on or off; see Turning an extension off.
The tour (Help › Burrow Tutorial) shows markdown-notes, reading-time and inspect running on sample notes, even when yours are off; nothing it does changes your config file.
markdown-notes
markdown-notes reads and writes folders of Markdown notes, such as Obsidian vaults, as many as you like. It’s kept apart from your blyg, so it works with no blyg connected, and it never links notes to posts.
- The notes panel (the notes drawer’s Notes tab) switches between your folders, lists the one you pick, searches titles and bodies, and opens a note to read or edit. Add folder… adds more; Remove from Burrow forgets a folder (its notes stay where they are).
- Copy into post puts the selection, or the whole note, into your draft at the cursor, as a quote or as it is (you pick).
- Save selection to notes, in the Extensions palette (⇧⌘P), makes a new note from the text you’ve selected.
It never adds frontmatter to a note (any frontmatter it finds is left as it is), and it never deletes a note. If a note changed on disk after you opened it, your save is refused, so it never overwrites the other change.
Turning it on
The easy way: Settings › Notes folders › Add folder…, then pick one or more folders. Burrow writes the config lines for you and asks for permission to use those folders.
Or add the lines to the config file (Burrow › Open Config File) yourself, then reload it (⇧⌘,):
extension = markdown-notes
extension-setting = markdown-notes vault=~/Notes
extension-setting = markdown-notes vault-work=~/Work/Vault
vault is your first folder of notes, and each vault-<label> adds another
(the label is lowercase letters, digits and dashes). There’s no default: with
no folder, the Notes tab offers Choose a folder…, and Burrow never reads a
folder you didn’t pick. It has two more settings:
# where new notes go, inside each folder (default: the top of the folder)
extension-setting = markdown-notes folder=Inbox
# how often it looks for changed notes, in milliseconds (default 2000)
extension-setting = markdown-notes poll-ms=5000
cross-post
cross-post puts a post you’ve published on Substack Notes too: its opening paragraph and the link back to your blyg. It runs in Burrow’s browser pane, where you’re signed in to Substack, and it never posts without you clicking Post.
Turning it on
extension = cross-post
extension-allow = cross-post items.read
extension-allow = cross-post ui
extension-allow = cross-post browser.automate:https://substack.com
The last line is the one that matters: it lets cross-post fill in, and when
you confirm, click Post on substack.com, signed in as you. It names that one
site exactly, so it can’t act anywhere else. If you leave the extension-allow
lines out, Burrow asks for them the first time it starts cross-post.
Sign in to Substack once, by hand, in the browser pane: press ⇧⌘P and
pick Open Substack Notes (or View › Open Browser… and type
substack.com). The pane remembers it (on macOS 14 or
later; on older macOS, sign in before each run). cross-post never
signs in for you, never sees your sign-in, and stops with “Sign in to Substack
in this pane” if you’re signed out.
Cross-posting
With a published post open, press ⇧⌘P and pick Cross-post to Substack Notes…. Every run shows two confirmation sheets, and no setting skips them:
- The text. You see the note it will post, and can edit it. Continue opens your Substack home feed in the pane, clicks “What’s on your mind?”, and puts the text in the note composer that opens.
- Post this to Substack Notes? You see what the notes box now holds. Post clicks Substack’s Post button; you can also click Post yourself, or cancel.
The run uses the same side pane as a link you click, with the sheets beside it so you can see the page. You can fold the pane (esc, ×, or ⇧⌘B) while it runs: the run keeps going, and the status bar shows macro running · show pane. Click that, or press ⇧⌘B, to bring the pane back. The pane comes back by itself before the text goes in, and for the second sheet. Afterwards it’s folded again if it was folded before, unless you folded or unfolded it during the run, or you still need the page (you chose to click Post yourself, or the run stopped with a problem).
The note is the post’s first paragraph of prose (headings, quotes, images and code are skipped, and Markdown marks are taken out), a blank line, and the link. The whole note is at most 280 characters, the link included, counted as a reader counts them (an emoji is one, and is never cut in half); the paragraph is cut on a word boundary with “…” to fit. Two settings change it:
# the note: {{excerpt}}, {{title}} and {{permalink}}; \n is a line break
extension-setting = cross-post template={{title}}\n\n{{excerpt}}\n\n{{permalink}}
# the longest note, link included (default 280)
extension-setting = cross-post max-chars=500
Before you rely on it
- The recipe was last checked on 2026-10-09 (
tested = "2026-10-09"): it opens Substack’s home feed, clicks “What’s on your mind?”, pastes into the composer that opens, and clicks its Post button. Substack can change its page at any time. When a step doesn’t find what it looks for, the run stops, says which step, and nothing is posted.blygger +list-extensionsshows each macro’stestedvalue. The extension’smacro.log(in Burrow’s data folder, underextensions/cross-post/) then lists the text boxes and buttons the page did have (tags, classes, labels such as “Post”; never the page’s text or yours), so the recipe can be fixed. Start Burrow withBLYGGER_MACRO_TRACE=1to log that after every step. - One post per click. cross-post posts once each time you confirm. It never posts on a schedule, in the background, or again by itself, and it waits at least 60 seconds between two runs.
- It’s your account. cross-post acts as you on Substack, the same as if you typed and clicked yourself. If Substack limits or acts on an account for automated posting, that’s your risk; check its terms.
reading-time and inspect
Two small extensions for what you read, ports of blygger-studio 0.39’s extensions of the same names, so the web Studio and Burrow agree:
- reading-time ends each post’s byline, in the stream and in Reader, with an estimated reading time (· 4 min); hover over it for the word count. It describes the text, not its audience.
- inspect adds ⋯ to each post’s actions. Click it, then inspect: the record Burrow holds for the post (ids, versions, references, hashes), and its JSON with the bodies left out, with a Copy button. It’s for people building clients, or checking why a mention or an import looks wrong.
Both need only reading.read, fetch nothing and use no network:
extension = reading-time
extension-allow = reading-time reading.read
extension = inspect
extension-allow = inspect reading.read
Or turn them on in ⇧⌘P › Manage extensions… (Turn on, then Allow).
Permissions and consent
The first time Burrow starts an extension, it shows what the extension asks
for, such as “markdown-notes wants to: read and write files in ~/Notes · show
messages”. You can untick any item (or press its number). Allow (⏎)
writes your answer into the config file as extension-allow lines, and
starts the extension; Not now (esc) leaves it waiting, with a notice in
the status bar that brings the sheet back; Turn off (⌘⌫) says you don’t
want it after all and turns it off:
extension-allow = markdown-notes ui
extension-allow = markdown-notes fs:~/Notes
You can also write these lines yourself, or delete one to take a permission back. Each line grants one capability:
| Capability | Lets the extension |
|---|---|
items.read | read your own posts, drafts and scratch notes |
items.write | create drafts and scratch notes, and edit their text (it can never publish) |
reading.read | read the posts held from your subscriptions |
blyg.identity | know your blyg’s address (never your sign-in) |
ui | show messages, and select a post in the list |
hooks:itemPublished, hooks:itemSaved, hooks:itemCreated | be told when you publish, edit or create a post, with its text |
browser.capture | read the page open in the browser pane when you run one of its commands (never cookies or sign-ins) |
browser.automate:<site> | fill in and, when you confirm, click Post on that one site, signed in as you |
fs:<folder> | read and write files in that folder |
net | use the network |
Burrow enforces the first ten: without the grant, the extension’s request
is refused. fs: and net are the extension’s own promise. An extension
is a program that runs as you, so Burrow can’t stop it from reading other
files or using the network. Only install extensions you trust.
Whatever it’s granted, no extension can publish, withdraw, pin, delete or fork a post, change your blyg’s settings, or see your sign-in, your Keychain or Burrow’s database.
Turning an extension off
In ⇧⌘P › Manage extensions…, each extension that’s on has:
- Turn off: removes its
extensionline, so it stops. Itsextension-allowandextension-settinglines stay, so Turn on later brings it back as it was, without asking again. - Forget permissions (on any extension that’s been allowed something,
on or off): turns it off and removes its
extension-allowlines too, so the next Turn on asks again.
Turn off works in any state: running, waiting for permission, or stopped
after crashing. Any question or notice about the extension goes with it.
Editing the config file does the same: delete the extension line to turn it
off, or its extension-allow lines to take permissions back.
Checking your extensions
blygger +list-extensions
lists every extension, bundled and installed, whether it’s on, what it’s
granted and what it still asks for, and any installed folder Burrow couldn’t
read. blygger +validate-config warns about an extension line that names
no installed extension, a permission an extension asks for but hasn’t been
granted, a grant it never asks for, and a setting it doesn’t have.
If an extension crashes, Burrow restarts it after 1, 5 and then 30 seconds.
After three failures in ten minutes it stays stopped until you reload the
config. Its error output is kept in a log in Burrow’s data folder, under
extensions/<name>/stderr.log.
Installing someone else’s extension
An extension is a folder with an extension.toml file in it. Copy the folder
into ~/.config/blygger/extensions/, so that you have
~/.config/blygger/extensions/<name>/extension.toml, and then turn it on with
extension = <name> like markdown-notes. There’s no store, download or
automatic update: you choose what to copy.
Writing an extension
An extension can be written in any language: a Python or Node script, a
shell script, or a compiled program. Burrow runs the command in its
manifest from the extension’s folder, and the two talk over its standard
input and output, one JSON-RPC 2.0 message per line. A small one:
name = "hello"
version = "0.1.0"
protocol = 1
description = "Says hello."
command = ["python3", "main.py"]
capabilities = ["ui"]
[[commands]]
id = "hello"
title = "Say hello"
when = "always"
The full protocol (the manifest, every method, the errors, the timeouts and
how Burrow restarts an extension) is in
Extensions (BXP), from docs/EXTENSIONS.md in the
repository.
For an extension in Rust, the blyg-ext crate has the protocol types and a
serve loop, and markdown-notes (crates/blyg-ext-notes) is a worked
example.
Submit your extension
Wrote an extension? List it on the
Community extensions page. The page is
made from one file in the repository, extensions/community.toml, every time
the documentation site is built, so a merged entry shows up on its own.
Community extensions are used at your own risk. The Burrow maintainers do not review, verify, audit or endorse them. An extension is a program that runs with your user’s rights, and
fs:andnetare its own declarations, not something Burrow can enforce. Read the source before you install one. To report a malicious or misleading listing, open an issue and it will be removed.
To submit one, open a pull request that adds an [[extension]] table to
extensions/community.toml, in alphabetical order by name:
[[extension]]
name = "wordcount" # the name in your extension.toml
title = "Word count"
description = "Shows the word count and reading time of the open draft."
repo = "https://github.com/example/burrow-ext-wordcount"
homepage = "https://example.com/wordcount" # optional
author = "example" # your GitHub handle, not an email
capabilities = ["items.read", "ui"] # exactly what your manifest asks for
platforms = ["macos", "windows"]
added = 2026-10-09 # a date, without quotes
If you’d rather not open a pull request, fill in the extension submission form and a maintainer will add the entry for you.
CI runs scripts/community-extensions.py check on the pull request (you can
run it yourself first). It checks that:
- the file is valid TOML, and every entry has all the fields above and no
others (only
homepageis optional); nameis lowercase kebab-case (word-count), at most 40 characters, not already listed, and not the name of an extension that comes with Burrow;title(at most 60 characters) anddescription(one line, at most 160) are plain text, with no HTML or Markdown code;repoandhomepagearehttps://addresses;authoris a GitHub handle, and no field holds an email address;- each capability is one Burrow knows (see
Permissions and consent), and
platformsismacos,windowsor both; - the entries are sorted by
name.
A maintainer merges an entry after a light look at its format, and nothing
more: the source code isn’t read, run or tested. List the source code’s
repository, keep capabilities in step with your manifest, and open another
pull request when the details change, or to take the entry down.