Building a plugin
Foundry plugins are regular Go packages that register themselves through the plugin registry and optionally implement one or more hook interfaces.
Plugin shape
plugins/myplugin/
plugin.go
plugin.yaml
assets/
css/
js/
Required metadata
Every plugin should ship a plugin.yaml file. The compatibility fields are
the ones that matter most operationally:
name: myplugin
title: My Plugin
version: 0.1.0
description: Adds project-specific behavior
author: Your Team
license: MIT
foundry_api: v1
min_foundry_version: 0.1.0
compatibility_version: v1
dependencies:
- name: github.com/acme/search-core
version: ^1.2.0
config_schema:
- name: mode
type: select
enum: [compact, full]
default: compact
admin:
pages:
- key: search-console
title: Search Console
route: /plugins/search
module: admin/search-console.js
styles:
- admin/search-console.css
settings_sections:
- key: search
title: Search
description: Search tuning
Available metadata fields
nametitleversiondescriptionauthorhomepagelicensereporequiresdependenciesfoundry_apimin_foundry_versioncompatibility_versionconfig_schemaadmin.pagesadmin.widgetsadmin.slotsadmin.settings_sectionsscreenshots
Plugin-defined admin pages
Plugins can now declare admin pages in plugin.yaml. Foundry exposes those
pages through the admin extension registry, gives them stable routes inside the admin
shell, and can auto-load a plugin-provided ES module bundle for the page.
admin:
pages:
- key: search-console
title: Search Console
route: /plugins/search
capability: plugins.manage
description: Inspect search status and rebuild controls
module: admin/search-console.js
styles:
- admin/search-console.css
The module and styles paths are relative to the plugin
directory. Foundry serves them under
<admin.path>/extensions/<plugin>/... after normalizing the
paths and keeping them inside the plugin root.
When that page is active, the default admin shell dispatches
foundry:admin-extension-page, exposes window.FoundryAdmin, and
will automatically import the plugin module if one is declared. A page bundle can export
either mountAdminExtensionPage(ctx) or a default function.
Widgets follow the same model. The default admin theme currently exposes these stable widget slots:
overview.afterdocuments.sidebarmedia.sidebarplugins.sidebar
Widget bundles can export mountAdminExtensionWidget(ctx) or a default
function. The shell dispatches foundry:admin-extension-widget for each
visible widget mount.
Minimal implementation
package myplugin
import "github.com/sphireinc/foundry/internal/plugins"
type Plugin struct{}
func (p *Plugin) Name() string { return "myplugin" }
func init() {
plugins.Register("myplugin", func() plugins.Plugin {
return &Plugin{}
})
}
Available plugin hooks
The current plugin surface is interface-based. A plugin implements only the hooks it needs.
OnConfigLoaded(*config.Config) errorOnContentDiscovered(path string) errorOnFrontmatterParsed(*content.Document) errorOnMarkdownRendered(*content.Document) errorOnDocumentParsed(*content.Document) errorOnDataLoaded(map[string]any) errorOnGraphBuilding(*content.SiteGraph) errorOnGraphBuilt(*content.SiteGraph) errorOnTaxonomyBuilt(*content.SiteGraph) errorOnRoutesAssigned(*content.SiteGraph) errorOnContext(*renderer.ViewData) errorOnAssets(*renderer.ViewData, *renderer.AssetSet) errorOnHTMLSlots(*renderer.ViewData, *renderer.Slots) errorOnBeforeRender(*renderer.ViewData) errorOnAfterRender(url string, html []byte) ([]byte, error)OnAssetsBuilding(*config.Config) errorOnBuildStarted() errorOnBuildCompleted(*content.SiteGraph) errorOnServerStarted(addr string) errorRegisterRoutes(mux *http.ServeMux)Commands() []plugins.Commandfor plugin-provided CLI commands
Injectable HTML slots
Slots are defined by the active theme. For the launch version of Foundry, the following
slot names are the required baseline supported by theme validation and are therefore the
safe targets for OnHTMLSlots:
head.endbody.startbody.endpage.before_mainpage.after_mainpage.before_contentpage.after_contentpost.before_headerpost.after_headerpost.before_contentpost.after_contentpost.sidebar.toppost.sidebar.overviewpost.sidebar.bottom
Plugin command surface
If a plugin implements CLI commands, each command currently exposes these fields:
NameSummaryDescriptionRun func(ctx plugins.CommandContext) error
Typical workflow
- Create the plugin directory under
plugins/. - Add
plugin.goandplugin.yaml. - Register the plugin in
init(). - Enable it in project config.
- Run
foundry plugin syncorgo run ./cmd/plugin-sync. - Run
go test ./...andfoundry plugin validate.
Asset and HTML integration
Plugins can contribute assets through the renderer asset hooks and add markup through
HTML slots. If a plugin ships public assets, place them under assets/;
Foundry will copy enabled plugin assets into public/plugins/<name>/.
Dependency and install notes
-
Use the
requiresfield inplugin.yamlwhen your plugin depends on another plugin by repo identity. -
Use
dependencieswhen you want richer dependency metadata such as version ranges or optional dependencies. -
Plugin installation is intentionally restricted to GitHub over
httpsorgit@github.com. -
Plugin updates now preserve rollback snapshots under
plugins/.rollback/<name>/. - Admin plugin records surface health diagnostics, compatibility fields, dependency metadata, and rollback availability.
- Installing a plugin still means trusting third-party code. Treat it as a supply-chain boundary.
Reference code
The built-in plugins are the best reference implementations:
- Reading Time for document enrichment and slot rendering.
- TOC for content parsing, asset injection, slot rendering, and plugin CLI commands.
- Related Posts for post-render content injection into post slots.