Overview

JSON and template driven user defined plugins: ```custom-fence-block {title: "greet.js"} function greet(name) { console.log("hello, " + name); } greet("world"); ``` :include-themed-box: Capacity approaching threshold { severity: "warning", tags: ["observability"] }

Registering A Plugin

List each plugin config under in extensions.json. Paths resolve relative to your documentation root, like and . plugins cssResources jsResources

Plugin Config Shape

is what you type after or as the fence language. A config registers exactly one role; if you need both an include and a fence variant of the same template, write two configs that point at the same they take different special arguments ( vs ) anyway. { "id": "themed-box", "type": "include", "template": "plugins/themed-box-plugin.ftl", "arguments": { "freeForm": { "required": true }, "severity": { "type": "string", "required": true, "limitValuesTo": ["info", "success", "warning"] }, "tags": { "type": "list-of-string", "limitValuesTo": "$plugins/themed-box-tags.json" } } } id :include- .ftl freeForm fenceContent

Arguments

Each entry under defines one parameter callers can pass. , , , , or defaults to optional; an inline array of allowed values, or a reference to a JSON array file. Omit it to accept any value of . Unknown arguments, type mismatches, and values outside all fail the build with the same error reporting as built-in plugins. Limit Values From A File Point at a JSON array when the allowed set is owned elsewhere a team-wide tag list, regional names, severity levels shared with another tool: The path resolves relative to the plugin config first, then falls back to znai's resource resolver so it works next to the , next to , on the lookup path, or packaged in a jar. arguments type number string list-of-number list-of-string list required false limitValuesTo $path/to/file.json type limitValuesTo limitValuesTo ["demo", "theme", "observability", "latency", "deployment"] .json toc

Special Arguments

Two argument names are reserved: the text right after the plugin id and before . Use it for the single primary input (a message, an entity name, a URL). No needed it's always raw text. Set to fail the build when the caller omits it; defaults to optional. the body of a fenced block; only meaningful for fence plugins. No needed the raw text is passed through untouched. Always present by construction. Both names skip the regular type/ validation. freeForm {...} type required: true fenceContent type limitValuesTo

Template

Templates are rendered with FreeMarker, same as include-template. Every argument including and is a top-level variable. The output can be any markdown znai understands: prose, fenced code, calls to other plugins. This example wraps include-javascript-function and forwards and , shrinking the surface area writers need to remember. freeForm fenceContent :include-javascript-function: themeBox { label: "${freeForm}", severity: "${severity}", tags: [<#list tags! as tag>"${tag}"<#sep>, </#list>], title: "${severity?upper_case} box title from template" } severity tags

Usage

themeBox Deploy finished in 4 minutes flat success deployment latency SUCCESS box title from template themeBox Capacity approaching threshold warning observability WARNING box title from template Omit a required argument or pass a tag missing from and the build stops with a descriptive error. Arguments without accept any value of the declared handy for free-text titles or counts. :include-themed-box: Deploy finished in 4 minutes flat { severity: "success", tags: ["deployment", "latency"] } :include-themed-box: Capacity approaching threshold { severity: "warning", tags: ["observability"] } themed-box-tags.json limitValuesTo type

Fence Plugins

Set to register a fenced block plugin. The body comes through the special argument: FreeMarker's escapes the body so it stays a valid JSON string when handed to another plugin's params block multi-line bodies, quotes, and backslashes survive intact. The fence marker is the plugin id, not a language hint: themeBox greet.js function greet(name) { console.log("hello, " + name); } greet("world"); The body reaches verbatim as the arg. Any fence plugin can use this pattern to forward content to diagram renderers, code annotators, or other include plugins. "type": "fence" fenceContent { "id": "custom-fence-block", "type": "fence", "template": "plugins/custom-fence-block-plugin.ftl", "arguments": { "fenceContent": {}, "title": { "type": "string" } } } :include-javascript-function: themeBox { title: "${title!'code snippet'}", snippet: "${fenceContent?json_string}" } ?json_string ```custom-fence-block {title: "greet.js"} function greet(name) { console.log("hello, " + name); } greet("world"); ``` include-javascript-function snippet

Rebuild Detection

The plugin config, the template, and any referenced files are treated as auxiliary files. Edit any of them and znai regenerates the affected pages same contract as template includes and snippet files. limitValuesTo

Search

Values you pass to the plugin flow into znai's local search via the inner plugin's . No extra work to make pages searchable by parameter content. textForSearch

When To Use

User-defined plugins fit when: you keep pasting the same include block with only a few values changed writers should pick from a constrained set (an enum-like , a tag registry) a snippet spans multiple plugins and you want one entry point that composes them consistently If the reuse is purely structural and needs no validation, include-template is enough on its own. severity