Plugin Cookbook
Pattern 1: Generate a File in dist/
Hook: dist:clean or content:ready
Use dist:clean when the file is independent of content. Use content:ready when you need access to loaded content and collections.
Example: @shevky/plugin-robots-txt writes robots.txt during dist:clean.
Pattern 2: Run an External Tool
Hook: assets:copy
Execute CLI tools for CSS/JS compilation using exec.execute() or exec.executeNpx() from @shevky/base.
Examples: @shevky/plugin-tailwindcss (runs Tailwind CLI), @shevky/plugin-esbuild (runs esbuild via npx).
Pattern 3: Inject Remote Content
Hook: content:load
Fetch data from an API and call ctx.addContent({ header, body, content, sourcePath, isValid }) to inject it into the content pipeline.
Example: @shevky/plugin-content-bridge fetches paginated API data with a mapping DSL.
Pattern 4: Generate Feeds or Sitemaps
Hook: content:ready
Iterate ctx.contentFiles and ctx.pages to produce XML output files.
Examples: @shevky/plugin-rss (RSS 2.0 feeds), @shevky/plugin-sitemap (sitemap.xml).
Pattern 5: Enrich Page Metadata
Hook: page:meta
Read ctx.frontMatter, compute additional metadata, and call ctx.setPageMeta(meta) to inject it into the rendering pipeline.
Example: @shevky/plugin-open-graph generates OG, Twitter Card, and JSON-LD data.
Best Practices
- Validate config early. Check for required settings before doing work. Log actionable warnings with
ctx.log.warn(). - Keep side effects explicit. Document what files your plugin creates or what data it mutates.
- Handle errors gracefully. A thrown error is caught and logged by the engine, but your plugin's output will be missing.
- Prefer
ctxhelpers. Usectx.file,ctx.path,ctx.lograther than directfsimports when possible. - Avoid depending on undocumented internal shapes. Stick to the documented context fields.
Anti-Patterns
- Mutating shared objects silently. If you write to
ctx.contentFilesentries, other plugins see your changes. Document this coupling. - Implicit plugin ordering. If your plugin depends on another plugin's output, document the required ordering in
plugins[]. - Large synchronous work in
page:meta. This hook runs per-page; keep it fast.