F Foundry Plugin authoring

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

  • name
  • title
  • version
  • description
  • author
  • homepage
  • license
  • repo
  • requires
  • dependencies
  • foundry_api
  • min_foundry_version
  • compatibility_version
  • config_schema
  • admin.pages
  • admin.widgets
  • admin.slots
  • admin.settings_sections
  • screenshots

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.after
  • documents.sidebar
  • media.sidebar
  • plugins.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) error
  • OnContentDiscovered(path string) error
  • OnFrontmatterParsed(*content.Document) error
  • OnMarkdownRendered(*content.Document) error
  • OnDocumentParsed(*content.Document) error
  • OnDataLoaded(map[string]any) error
  • OnGraphBuilding(*content.SiteGraph) error
  • OnGraphBuilt(*content.SiteGraph) error
  • OnTaxonomyBuilt(*content.SiteGraph) error
  • OnRoutesAssigned(*content.SiteGraph) error
  • OnContext(*renderer.ViewData) error
  • OnAssets(*renderer.ViewData, *renderer.AssetSet) error
  • OnHTMLSlots(*renderer.ViewData, *renderer.Slots) error
  • OnBeforeRender(*renderer.ViewData) error
  • OnAfterRender(url string, html []byte) ([]byte, error)
  • OnAssetsBuilding(*config.Config) error
  • OnBuildStarted() error
  • OnBuildCompleted(*content.SiteGraph) error
  • OnServerStarted(addr string) error
  • RegisterRoutes(mux *http.ServeMux)
  • Commands() []plugins.Command for 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.end
  • body.start
  • body.end
  • page.before_main
  • page.after_main
  • page.before_content
  • page.after_content
  • post.before_header
  • post.after_header
  • post.before_content
  • post.after_content
  • post.sidebar.top
  • post.sidebar.overview
  • post.sidebar.bottom

Plugin command surface

If a plugin implements CLI commands, each command currently exposes these fields:

  • Name
  • Summary
  • Description
  • Run func(ctx plugins.CommandContext) error

Typical workflow

  1. Create the plugin directory under plugins/.
  2. Add plugin.go and plugin.yaml.
  3. Register the plugin in init().
  4. Enable it in project config.
  5. Run foundry plugin sync or go run ./cmd/plugin-sync.
  6. Run go test ./... and foundry 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 requires field in plugin.yaml when your plugin depends on another plugin by repo identity.
  • Use dependencies when you want richer dependency metadata such as version ranges or optional dependencies.
  • Plugin installation is intentionally restricted to GitHub over https or git@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.