Plugin Structure and Metadata

Every plugin is a pair of files in its own directory: a compiled WASM component and a TOML manifest that tells Accent CMS what the plugin can do. This page covers the directory layout and every section of the plugin.toml file.

Plugin Directory Structure

Plugins live in site/plugins/, with each plugin in its own subdirectory:

site/
  plugins/
    syntax-highlight/
      plugin.toml          # Metadata (required)
      plugin.wasm          # Compiled WASM module (required)
    contact-form/
      plugin.toml
      plugin.wasm
    search/
      plugin.toml
      plugin.wasm

Each plugin directory must contain both files:

  • plugin.toml - Declares the plugin’s name, version, and capabilities
  • plugin.wasm - The compiled WebAssembly component built with a Component-Model toolchain (cargo-component or jco)

If either file is missing, the plugin is skipped with a warning. Invalid plugins do not prevent the server from starting.

Plugin Metadata (plugin.toml)

The plugin.toml file declares what a plugin can do. Here is a complete example:

[plugin]
name = "syntax-highlight"
version = "0.1.0"
api_version = "0.2.0"
description = "Syntax highlighting for code blocks"

[hooks]
on_render = true             # Called after markdown-to-HTML rendering
on_page_load = false         # Called before markdown processing
on_media_discover = false    # Called during content scanning for media files
on_media_process = false     # Called after image processing

[filters]
# filter_name = "exported_function_name"
highlight = "apply_highlight"
lang_name = "get_language_name"

[shortcodes]
# Tags this plugin renders; each needs a manifest in shortcodes/
codes = ["code", "highlight"]

[routes]
# path = "HTTP method"
"/api/highlight" = "POST"
"/api/languages" = "GET"

[config]
# Plugin-specific settings (read via the config capability)
theme = "monokai"
line_numbers = true

Required Fields

The [plugin] section is required:

FieldTypeDescription
namestringUnique plugin identifier (used as registry key)
versionstringSemantic version of the plugin
api_versionstringHost API version the plugin targets (checked against the host’s supported range; see Security)
descriptionstringHuman-readable description (optional)
requires_fsboolRequest scoped WASI filesystem access (optional, default: false)
allowed_hostsarrayHosts the plugin may reach over outbound HTTP (optional; see Security)
host_servicesarrayHost services the plugin may call through the typed host-services capability: "mail", "data" (optional; see Security)

Hooks

The [hooks] section declares which lifecycle hooks the plugin listens to:

HookWhen it firesInputOutput
on_page_loadBefore markdown processingRaw markdownModified markdown
on_renderAfter HTML renderingRendered HTMLModified HTML
on_media_discoverDuring content scanningFile path, MIME typeMetadata key-value pairs
on_media_processAfter image processingRaw image bytes, dimensionsModified bytes, metadata

Multiple plugins can register for the same hook. They are called in the order they were loaded.

The on_media_discover and on_media_process hooks are described in detail in the Media Hooks section.

Filters

The [filters] section declares the template filter names the plugin provides:

[filters]
highlight = "apply"    # {{ code | highlight("rust") }}
uppercase = "apply"    # {{ text | uppercase }}

The key is the filter name used in templates. The host dispatches every filter to the component’s single apply export, keyed on the filter name, so the value is simply that export.

Shortcodes

The [shortcodes] section lists the tags your plugin renders. The plugin exports a render function from the shortcodes interface, and the host calls it with the tag name, the tag’s attributes, its body, and the page path.

[shortcodes]
codes = ["note", "warning"]

Each tag also needs a component manifest in the plugin’s shortcodes/ directory, in the same format a theme uses. The manifest is the tag’s schema: content that names the tag is validated against it.

my-plugin/
├── plugin.toml
├── plugin.wasm
└── shortcodes/
    ├── note.yaml
    └── warning.yaml
# shortcodes/note.yaml
name: note
description: A highlighted note.
params:
  - name: title
    type: string
body: markdown

Attribute values reach the plugin as strings: a number or a boolean as you wrote it, and a list or an object as JSON. The body is the tag’s content as Markdown, with any tags inside it already expanded. If the plugin fails to render a tag, the tag stays in the page as you wrote it, and the server log says why.

accent validate reports a tag listed in codes with no manifest, and a manifest for a tag codes does not list. Until you fix it, none of the plugin’s tags register.

What a plugin tag may do

Installing a plugin does not by itself put markup in your pages. Two settings in config.yaml decide what plugin tags may do:

SettingDefaultEffect
plugins.components.schemastrueRegisters the manifests plugins ship, so content using their tags is validated.
plugins.components.renderfalseLets plugins write the markup for their tags.

With the defaults, content that uses a plugin tag is validated, and the tag stays in the page as you wrote it. The server log names the plugin and the setting that turns rendering on. To let plugins render their tags:

plugins:
  components:
    render: true

Rendering is off by default because a plugin’s markup goes into every page that uses its tag, and the plugin sandbox limits what a plugin can compute, not what markup it returns. Turn it on for plugins you trust.

The two settings are independent. With schemas: false and render: true, plugins render their tags, and content using them is not validated. accent validate lists each use at Info level, so the choice stays visible without failing the run.

A built-in tag or a theme component with the same name as a plugin tag wins, and accent validate reports the plugin’s tag as shadowed.

Plugin tags in accent validate

  • Content that uses a plugin tag incorrectly is an error, like any other tag.
  • A tag declared by a plugin that did not load names the plugin and the reason, for example that plugins are disabled.
  • With plugin schemas off and rendering on, each use is reported at Info level, because its content is not validated. It does not fail the run.
  • A plugin tag whose manifest does not register is reported as not available, with the reason. The plugin’s other tags, other plugins’ tags, and every other tag on the site are still served and checked.

While a plugin is missing or still in development, run accent validate --lax. The run reports these findings as warnings, prints how many it relaxed, and does not fail on them.

To see which tags come from which plugin, and whether each one renders, run accent query components.

Routes

The [routes] section registers custom HTTP endpoints:

[routes]
"/api/search" = "GET"
"/api/contact" = "POST"

Plugin Configuration

The [config] section holds plugin-specific settings. A plugin reads them at runtime through the config capability (config::get(key) / config::get_all()); they merge under any overrides from plugins.config in config.yaml:

[config]
theme = "monokai"
line_numbers = true
max_results = 50

A minimal plugin.toml only needs the [plugin] section:

[plugin]
name = "my-plugin"
version = "0.1.0"
api_version = "0.2.0"