1# Plugins
2
3How to write and register a Webette plugin with the current API.
4
5## Quick start
6
71) Create an ESM file under the site root, e.g. `_plugins/my-plugin.js`.
82) Export an object with any hooks you need.
93) Reference it in `webette.config.ts`:
10```ts
11export default {
12 // ...
13 plugins: ["_plugins/my-plugin.js"],
14};
15```
16
17## Hooks you can implement
18
19- `onInit(ctx)` — after env/site load, before scan.
20- `onScan(site, ctx)` — after scan/routes, before content resolution. Return a new site or mutate in place.
21- `onResolved(site, ctx)` — after content resolution, before timestamps/export. Return a new site or mutate in place.
22- `transformBlock(block, ctx)` — per-block mutation (depth-first). Return a new block or mutate. `ctx` includes `entry` and `collection`.
23- `transformEntry(entry, ctx)` — per-entry mutation after blocks. Return a new entry or mutate. `ctx` includes `collection`.
24- `extendTemplates(handlebars, ctx)` — register helpers/partials/layouts before templates are compiled.
25
26Hooks run in that order: init -> scan -> resolved -> transformBlock -> transformEntry -> extendTemplates.
27
28## Context (`ctx`)
29
30Always available:
31- `rootDir` — absolute site root.
32- `env` — loaded `webette.tool.ts` config.
33- `logger` — child logger for your plugin.
34- `readContent` — whether the build is allowed to read block contents.
35- `exportDir` — `_public/_model` (resolved).
36- `pluginId` — the id of this plugin (filename-based if not provided).
37- `addExpectedOutput(relativePath)` — whitelist one file (relative to output dir) so prune keeps it.
38- `addExpectedDir(relativeDir)` — whitelist a whole directory/prefix under the output; all files inside are kept by prune.
39
40Extra in some hooks:
41- `collection` (transformEntry/transformBlock)
42- `entry` (transformBlock)
43
44## Prune and assets
45
46When `overwrite=replace-files`, Webette deletes any file not expected. If your plugin writes assets or pages:
47- Use `addExpectedOutput("assets/images/plugins/foo/thumb.webp")` for single files.
48- Use `addExpectedDir("assets/images/plugins/foo")` if you generate many files under one folder.
49Paths are always relative to the output folder (e.g. `_public` by default).
50
51## Template extensions
52
53`extendTemplates(handlebars, ctx)` lets you add:
54- helpers: `handlebars.registerHelper("name", fn)`
55- partials: `handlebars.registerPartial("name", templateString)`
56- layouts: write a file and register it as a partial; reference by name from templates.
57
58Site templates override tool templates. In serve mode, the site templates are watched if present; otherwise the tool templates are watched.
59
60## Simple example
61
62```js
63export default {
64 pluginId: "example",
65
66 onResolved(site, ctx) {
67 ctx.logger.info("plugin.example", { entries: site.collections?.length ?? 0 });
68 },
69
70 transformBlock(block, ctx) {
71 if (block.type !== "image") return;
72 // Keep all generated variants under this folder
73 ctx.addExpectedDir(`assets/images/plugins/${ctx.entry.slug}`);
74 return {
75 ...block,
76 content: {
77 ...block.content,
78 note: "handled by example plugin",
79 },
80 };
81 },
82};
83```
84
85Plugins run locally with full access; there is no sandbox. Keep paths relative to the output dir when using prune APIs.