Writing a plugin
The folio Plugin contract lets you ship your own engine (a new command), transform (HTML post-processing), provider (new LLM/image API), or publish target.
EnginePlugin in 5 minutes
Create a new npm package folio-engine-poll (or @youorg/engine-poll):
// index.ts
import type { EnginePlugin } from "@foliolang/cli/plugin/contract";
import { contentHash } from "@foliolang/cli/build/canonical";
const PollPlugin: EnginePlugin = {
id: "engine-poll",
kind: "engine",
cmd: "/插入投票",
effects: [],
cacheKey: (block) => contentHash({ body: block.body }),
async run(block, ctx) {
const lines = block.body.split("\n").filter(Boolean);
const question = lines[0];
const options = lines.slice(1).map((o) => `<li>${o}</li>`).join("");
return { html: `<div class="poll"><h4>${question}</h4><ul>${options}</ul></div>` };
},
};
export default PollPlugin;
package.json:
{
"name": "folio-engine-poll",
"main": "index.ts",
"type": "module"
}
A user installs it:
npm install folio-engine-poll
On startup, folio scans node_modules for any package whose name starts with folio-engine- or @org/folio-engine- and registers it automatically.
In markdown:
::: /插入投票
Which widget do you like best?
filterable-table
tabs
slider-calc
:::
Other plugin kinds
- TransformPlugin — transforms the HTML string or hast tree (e.g. TOC injection, KaTeX rendering).
- PublishPlugin — new publish targets (e.g. Vercel, Surge.sh).
- ProviderPlugin — new LLM or image providers (e.g. Claude API, Stable Diffusion).
Full contract
src/plugin/contract.ts is the authoritative definition. Every field (effects, requires, cacheKey, estimateCost) has its semantics documented inline.
Full tutorial
- plugin-guide.md (GitHub) →
- Reference implementation: @folio/engine-poll source →