garden-plugin-author
Create, test, and publish plugins for the Digital Garden Eleventy template (oleeskild/digitalgarden, used by the obsidian-digital-garden plugin and Forestry.md). Use when asked to build a garden plugin, add a feature to a digital garden as a plugin, write or edit a garden-plugin.json manifest, or publish a plugin to the community registry.
- License 许可
- MIT
- 最近更新
- 2026-11-09
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- garden-plugin-author
- description
- Create, test, and publish plugins for the Digital Garden Eleventy template (oleeskild/digitalgarden, used by the obsidian-digital-garden plugin and Forestry.md). Use when asked to build a garden plugin, add a feature to a digital garden as a plugin, write or edit a garden-plugin.json manifest, or publish a plugin to the community registry.
03 SKILL.md 正文
Authoring a Digital Garden plugin
A garden plugin is a directory that extends a Digital Garden site: markup
injected into named layout slots, site-wide styles/scripts, and build-time
Eleventy/markdown-it hooks. Plugins are distributed as public GitHub repos
with a garden-plugin.json manifest at the repo root, and installed into
src/plugins/<id>/ of a garden repo. Follow this skill end to end; do not
invent extension points that are not listed here.
Where to work
- Inside a garden or template checkout (a repo with
.eleventy.jsandsrc/site/): create your plugin directly atsrc/plugins/<id>/and it runs immediately. - Standalone plugin repo: develop the plugin as its own directory, and
for testing clone https://github.com/oleeskild/digitalgarden, run
npm install, and symlink or copy your plugin intosrc/plugins/<id>/.
The plugin id must match ^[a-z0-9][a-z0-9-]*$, must equal the directory
name, and must NOT start with dg- (reserved for first-party plugins).
Minimal viable plugin
src/plugins/reading-time/
garden-plugin.json
templates/badge.njk
{
"id": "reading-time",
"name": "Reading Time",
"version": "1.0.0",
"description": "Shows an estimated reading time under the note title.",
"author": "Your Name",
"slots": { "notes.header": "templates/badge.njk" },
"noteSettings": ["dgReadingTime"]
}
{% if settings.dgReadingTime === true %}
<div class="reading-time">{{ ((content | striptags).split(" ").length / 200) | round }} min read</div>
{% endif %}
id, name, version, description, author are required. Everything
else is optional. All declared paths must be relative, use /, and
contain no .. — a violation makes the loader (and installers) reject
the plugin.
Slots
Slot templates are plain Nunjucks fragments (no front matter). They are
rendered with the full Eleventy data cascade — meta, settings,
title, tags, created, updated, content, collections, graph,
filetree, page — plus pluginSettings (this plugin's resolved
settings). Always self-gate on your own setting: every enabled plugin's
slots render unconditionally.
| Slot | Renders |
|---|---|
common.head / notes.head / index.head |
in <head> (all pages / notes / home page) |
common.header / notes.header / index.header |
in <header> after title and tags |
common.beforeContent / notes.beforeContent / index.beforeContent |
in <main> before the content |
common.afterContent / notes.afterContent / index.afterContent |
in <main> after the content |
common.footer / notes.footer / index.footer |
end of <body> (scripts, overlays, tooltips) |
navbar.actions |
navbar, where the search button sits |
filetree.actions |
filetree sidebar below the site title |
filetree.beforeTitle / filetree.afterTitle |
around the filetree site title |
sidebar.top / sidebar.bottom |
right-hand sidebar |
floating.bottomRight |
fixed stack in the bottom-right corner, filled from the corner upward; core's page panel toggle sits nearest the corner. Render one element per template (a button or link), it becomes a flex item and stacks above the previous one |
Manifest form: "slots": { "<slot>": "file.njk" } or a list of files.
Use common.footer for anything with a <script> or overlay markup.
Render order within a slot is the site owner's call, not the plugin's:
plugins render by the order number on their entry in
src/plugins/plugins.json (lower first, default 0), then by id. In
floating.bottomRight a lower order sits nearer the corner. Example:
{"plugins": {"theme-switcher": {"order": 10}, "random-note": {"order": 20}}}.
Regions (exclusive replacement)
A region replaces a core UI area instead of adding to it — at most one
enabled plugin provides each region (first by id wins; conflicts warn).
Currently: navigation (default: the core navbar; the shipped
dg-filetree plugin claims it to render the folder-tree sidebar).
"regions": { "navigation": "templates/my-nav.njk" }
A custom navigation plugin gets the full data cascade, including the
core-computed filetree data structure, and may include core components
(e.g. fall back to {% include "components/navbar.njk" %}) and render
sub-slots via components/pluginSlot.njk. Users switch navigations by
disabling dg-filetree and enabling yours. Read
src/plugins/dg-filetree/ in the template as the reference.
A navigation plugin MUST render the surfaces other extensions rely on,
in its own markup: the navbar.actions and filetree.actions plugin
slots (the search button lives there), and the site owner's custom
filetree components —
{% for imp in dynamics.filetree.beforeTitle %}{% include imp %}{% endfor %}
plus the same for dynamics.filetree.afterTitle. dg-filetree does
both; copy its template as the starting point.
Build hooks ("hooks": "index.js")
module.exports = {
// Both optional. context = { settings, manifest, pluginDir }
setupMarkdown(md, context) { md.use(require("markdown-it-footnote")); },
setupEleventy(eleventyConfig, context) {
eleventyConfig.addFilter("myFilter", (v) => v);
// To emit a page (needs collections), register a virtual template:
// eleventyConfig.addTemplate("my-page.njk",
// require("fs").readFileSync(require("path").join(context.pluginDir, "templates/page.njk"), "utf8"),
// { permalink: "/my-page.json", eleventyExcludeFromCollections: true });
},
};
Rules: hooks run at config time in Node; they may only require packages
the template already ships (check the template's package.json) or pure
JS files vendored inside the plugin dir — plugins cannot add npm
dependencies. Hook changes need a dev-server restart. Thrown errors are
caught and the hook is skipped with a [plugins] warning.
Settings and per-note flags
"settings": [
{ "key": "label", "name": "Label", "description": "…",
"type": "text", // text | boolean | number | select
"default": "Hello",
"options": ["a", "b"], // select only
"env": "MY_ENV_VAR" } // optional env fallback
],
"noteSettings": ["dgMyFlag"]
- Setting resolution: stored value in
src/plugins/plugins.json→ env var (envor the key) from the garden's.env→default. Read them aspluginSettings.<key>in templates,context.settings.<key>in hooks. noteSettingskeys (camelCase,dg-prefixed by convention) resolve like core note settings: env var of the same name is the global default, a note's frontmatterdg-my-flag: true/falseoverrides per note. Read assettings.<key>in templates.
Styles, scripts, assets
"styles": ["styles/x.scss"]—.scsscompiled per file (_partialsskipped) to/plugins/<id>/styles/x.cssand linked in<head>; plain.csscopied and linked as-is. Prefer the garden's--dg-*CSS variables for colors/spacing so themes keep working."scripts": ["assets/client.js"]— copied and loaded via<script defer>in<head>."assets": ["assets/"]— copied to/plugins/<id>/…, referenced only by your own code.
Test before publishing
From the template/garden checkout:
npm installonce, thennpm run dev— slot template, style, andplugins.jsonedits hot-reload; restart after hook or manifest changes.- Watch for
[plugins] …warnings in the build log — each one is a manifest or runtime problem in your plugin. - Build once with the plugin disabled
(
src/plugins/plugins.json→{"plugins": {"<id>": {"enabled": false}}}) and confirm the site is unchanged. - If the template has a test suite,
npm testmust stay green.
Publish
- Put the plugin at the root of a public GitHub repo
(
garden-plugin.jsonat top level). Suggested repo name:garden-plugin-<id>. - Add a README (what it does, a screenshot, settings) and a screenshot file — the community directory shows it.
- Tag a release matching the manifest version:
git tag v1.0.0 && git push --tags. Installers fetch the latest release tag, falling back to the default branch only when no release exists. Every future change: bumpversion, tag again. - Users can now install it by pasting the repo URL, or manually by
copying the directory into
src/plugins/<id>/. - To list it in the community directory, open a PR against
oleeskild/digitalgarden-pluginsadding one entry tocommunity-plugins.json:{ "id": "<id>", "name": "…", "author": "…", "description": "…", "repo": "you/garden-plugin-<id>", "screenshot": "screenshot.png" }.
Hard rules
- Never require install-time execution: no postinstall steps, no "run this script to finish setup". Installation is file copying only.
- A plugin must never break the build when misconfigured — the loader warns and skips, but keep your own code defensive too.
- Don't touch files outside your plugin directory or claim another
plugin's id. Don't use the
dg-prefix. - Plugin code runs in the site build and in visitors' browsers — keep it small, readable, and dependency-light so users can audit it.
For deeper reference, read the first-party plugins under src/plugins/
in the template repo — dg-link-preview is the smallest, dg-search
the most complete, and dg-filetree the navigation reference.
04 同仓库更多 Skills
obsidian-markdown
Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
obsidian-cli
Interact with Obsidian vaults using the Obsidian CLI to read, create, search, and manage notes, tasks, properties, and more. Also supports plugin and theme development with commands to reload plugins, run JavaScript, capture errors, take screenshots, and inspect the DOM. Use when the user asks to interact with their Obsidian vault, manage notes, search vault content, perform vault operations from the command line, or develop and debug Obsidian plugins and themes.
obsidian-bases
Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
json-canvas
Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.
digitalgarden
AI is knowledgeable about the inner workings of the Digital Garden plugin for Obsidian
defuddle
Extract clean markdown content from web pages using Defuddle CLI, removing clutter and navigation to save tokens. Use instead of WebFetch when the user provides a URL to read or analyze, for online documentation, articles, blog posts, or any standard web page. Do NOT use for URLs ending in .md — those are already markdown, use WebFetch directly.