hooks - moss
hooks
Build pipeline#
When moss compiles a site, it runs through these stages in order:
Scan folder
→ process hooks (pre-generation)
→ generate hook (build HTML)
→ enhance hooks (inject into slots)
→ deploy hook (push to hosting)
→ syndicate hooks (POSSE to platforms)
Plugins attach to stages by declaring capabilities in their manifest.
Hook reference#
Hook Arity Context Description
processmultiple ProcessContextPre-process source files before generation (e.g. download remote assets, transform markdown).
generatesingle GenerateContextBuild/transform source files to HTML output, replacing the default moss build pipeline.
enhancemultiple EnhanceContextProvide HTML for named template slots after generation (head-end, footer-end, after-article, etc.).
deploysingle DeployContextDeploy the built site to a hosting platform (e.g. GitHub Pages, Netlify).
syndicatemultiple SyndicateContextPOSSE-distribute published content after deployment (e.g. cross-post to Matters, RSS).
importmultiple ProcessContextImport content from an external source into the project folder (e.g. Matters profile, RSS feed).
process#
Runs before HTML generation. Multiple plugins can have this capability.
Use for: fetching external data, transforming source files, pre-processing content.
Context passed to the hook:
Field Type Description
project_pathstring Absolute path to the project folder
moss_dirstring Path to .moss/ directory
project_infoobject total_files, homepage_file, site_name, lang
configobject Plugin configuration values
Data contract: A process hook that fetches external data writes JSON to .moss/data/social/<plugin>.json. The build core does not read this file; enhance hooks and other plugins consume it.
The generated source-to-output map (paths plus uids) is at .moss/build/article-map.json.
generate#
Builds or transforms source content into HTML output. Only one plugin can have this capability; it replaces moss's built-in generator.
Use for: alternative SSG backends (Hugo, Astro, Jekyll).
Context: Same as process, plus output_dir, source_files (categorized by type), and site_config.
enhance#
Injects content into named template slots after HTML is generated. Multiple plugins can have this capability.
Use for: comments, analytics, newsletter forms, custom scripts.
Template slots#
Slots are named injection points in the HTML template where plugins can insert content.
Slot Position Authorable
head-endBefore — for stylesheets, scripts, and meta tags. no
after-titleInside , after the title/date row — for article metadata (e.g. book block, review colophon). no
before-article-endInside , before — for article addenda. no
after-articleBetween and — for comments, reactions (NOT part of the article). no
footer-shapeThe data-moss-shape attribute value on the open tag. Advanced: controls footer chrome mode. no
footer-leftInside footer, leading position — filled by footer.md or any file with slot: footer-left frontmatter. yes
footer-endInside footer, trailing position — for the auto-injected subscribe form and plugin widgets. no
body-endBefore
— for scripts that must run after DOM is ready.
no
How slots work#
During generation, moss writes HTML comment markers at each slot position:
<article>
<h1>Page Title</h1>
<!-- slot:after-title -->
<p>Content...</p>
<!-- slot:before-article-end -->
</article>
<!-- slot:after-article -->
During the enhance phase , each plugin with the enhance capability returns content for the slots it fills. moss replaces the markers with that content. Unfilled markers are stripped from the final output and never appear in the published HTML.
Multiple plugins can write to the same slot. Their content is concatenated in plugin load order.
EnhanceResult#
The enhance hook returns an object mapping slot names to HTML strings:
async enhance(ctx) {
return {
slots: {
"after-article": `<section class="comments">
<script src="https://comments.example/embed.js"></script>
</section>`,
"head-end": `<link rel="stylesheet" href="/comments.css">`
}
};
}
Include only the slots your plugin fills. Omitted slots are left for other plugins or stripped.
Zero-flicker preview#
During preview, moss rebuilds the site on every file change. To prevent flickering during rebuilds:
New output is built to .moss/site-stage/.
The preview server atomically switches its pointer to site-stage/.
The staged content is copied to .moss/site/ (the canonical directory).
The pointer switches back to .moss/site/.
The preview server never serves from a half-built directory. The switch is instant — a pointer update, not a file rename.
Returns: An EnhanceResult with slot content:
{
slots: {
"after-article": "<div class='comments'>...</div>",
"body-end": "<script src='analytics.js'></script>"
}
}
deploy#
Pushes the compiled site to a hosting platform. Only one plugin can have this capability.
Use for: GitHub Pages, Netlify, or custom hosting.
Context: Includes site_files (all compiled output), deployment info, and domain.
syndicate#
Distributes published content to external platforms (POSSE). Multiple plugins can have this capability.
Use for: cross-posting to Matters.town, Substack, social media.
Context: Includes articles (published URLs and metadata) and deployment info.
Plugin runtime#
Plugins run in the Tauri webview. The lifecycle:
Rust backend sends plugin code and manifest.
Plugin code is injected as a <script> tag.
Plugin creates a global object (e.g., window.MattersPlugin).
moss calls onload({ project_path, config }) if defined.
Hooks are called with their respective contexts.
Results are sent back to the Rust backend.
console.log, console.warn, and console.error from plugins are forwarded to the moss terminal.
Plugin modes#
How plugins run depends on the compilation mode:
Mode Behavior
Blocking moss compile: waits for process hooks to complete
NonBlocking Preview mode: fires process hooks but doesn't wait
SlotsOnly Watch rebuilds: skips process/generate, collects enhance slots only
Skip --no-plugins: bypasses all plugin hooks