Building a Plugin
This page walks through writing your first Fern plugin from scratch. By the end you will have a working plugin that adds a keybinding and a command, and you will know where to look to go further.
Prerequisites
Fern plugins are written in Lua 5.4. If you have not used Lua before, the official Getting started with Lua page is a good starting point and introduction to Lua. You don't need to install the Lua runtime, as Fern embeds one, but if you do decide to, adhere to the above version.
You also need plugins enabled in your config:
[plugins]
enabled = true
Step 1 - Create the plugin directory
Every plugin lives in its own subdirectory inside your plugins folder.
~/.config/fern/plugins/
daily-opener/
main.lua
Create that structure now:
mkdir -p ~/.config/fern/plugins/daily-opener
touch ~/.config/fern/plugins/daily-opener/main.lua
Step 2 - Write a minimal plugin
Open main.lua in any editor and add:
fern.notify("daily-opener loaded")
Start Fern. Because this plugin was not installed through the plugin manager,
Fern blocks it and shows a warning in the status bar. See
Plugin approval for details and follow
them to approve the new daily-opener plugin.
Once approved, Fern loads the plugin and you should see the notification briefly appear. That confirms the file is being executed correctly.
Step 3 - Open today's journal with a keybinding
Replace the contents of main.lua with:
fern.map("normal", "gd", function()
local t = fern.now()
local stem = string.format("%04d-%02d-%02d", t.year, t.month, t.day)
fern.open(stem, "journal")
fern.notify("opened " .. stem)
end)
Restart Fern (plugin code runs once at startup). In Normal mode press gd
and Fern opens today's journal entry, creating it if it does not exist.
What each line does:
| Line | API used | Purpose |
|---|---|---|
fern.map(...) |
fern.map |
Registers the keybinding for Normal mode |
fern.now() |
fern.now |
Returns a table with year, month, day, etc. |
string.format(...) |
Lua stdlib | Builds an ISO date stem like 2026-10-16 |
fern.open(stem, "journal") |
fern.open |
Opens (or creates) the journal entry |
fern.notify(...) |
fern.notify |
Shows a message in the status bar |
Step 4 - Add a command
Commands let users invoke behaviour from the command palette (ctrl+p) or
the command bar (:). Add a second registration below the keybinding:
fern.map("normal", "gd", function()
local t = fern.now()
local stem = string.format("%04d-%02d-%02d", t.year, t.month, t.day)
fern.open(stem, "journal")
fern.notify("opened " .. stem)
end)
fern.add_command("today", function()
local t = fern.now()
local stem = string.format("%04d-%02d-%02d", t.year, t.month, t.day)
fern.open(stem, "journal")
end)
After restarting you can invoke today from the command palette (ctrl+p)
or run :today from the command bar.
Step 5 - Persist state across sessions
fern.store_set and fern.store_get give each plugin its own isolated
key-value store backed by the vault database. Add a visit counter:
local function open_today()
local t = fern.now()
local stem = string.format("%04d-%02d-%02d", t.year, t.month, t.day)
fern.open(stem, "journal")
local count = tonumber(fern.store_get("visits") or "0") + 1
fern.store_set("visits", tostring(count))
fern.notify("opened " .. stem .. " (visit #" .. count .. ")")
end
fern.map("normal", "gd", open_today)
fern.add_command("today", open_today)
The count persists between restarts because it is written to the vault's
fern.db file.
Where to go from here
- Lua API Reference: full reference for every
fern.*function: commands, keybindings, events, timers, block renderers, vault queries, and more. - Fern-Terranium: the official plugin registry. If you build something useful, consider publishing it there so other Fern users can install it with a single keypress.
- Managing Plugins: how to install, update, remove, and approve plugins from inside Fern.