Plugins
Plugins extend Zeno's functionality through simple hooks. Add features without bloating the core.
Using Plugins
Add to zeno.config.json
:
{
"plugins": [
{ "name": "toc" },
{ "name": "analytics", "options": { "id": "GA-123" } }
]
}
Plugins run automatically during build.
Built-in Plugins
toc - Table of Contents
{
"name": "toc",
"options": {
"minHeadings": 2,
"maxLevel": 4
}
}
analytics - Google Analytics
{
"name": "analytics",
"options": {
"trackingId": "GA-XXXXXXXXX"
}
}
Plugin Hooks
Plugins use hooks to modify content:
Hook | When | Purpose |
---|---|---|
onMarkdownParse |
Before HTML conversion | Modify markdown |
onRenderHTML |
After HTML generation | Modify final HTML |
onPostBuild |
After build complete | Run cleanup tasks |
Creating Plugins
Create plugins/my-plugin.js
:
export default function myPlugin(options = {}) {
return {
name: 'my-plugin',
onMarkdownParse(markdown) {
// Modify markdown before HTML conversion
return markdown.replace(/TODO:/g, '⚠️ TODO:');
},
onRenderHTML(html, frontmatter) {
// Modify final HTML
return html.replace('</head>',
'<meta name="author" content="Me"></head>'
);
},
onPostBuild(buildData) {
// Run after build
console.log(`Built ${buildData.posts.length} posts`);
}
};
}
Use in config:
{
"plugins": [
{ "name": "my-plugin" }
]
}
Quick Examples
Add Google Analytics
// plugins/analytics.js
export default function analytics(options) {
return {
name: 'google-analytics',
onRenderHTML(html) {
const script = `
<script async src="https://www.googletagmanager.com/gtag/js?id=${options.id}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${options.id}');
</script>
`;
return html.replace('</head>', script + '</head>');
}
};
}
Add Reading Time
// plugins/reading-time.js
export default function readingTime(options = {}) {
return {
name: 'reading-time',
onMarkdownParse(markdown) {
const words = markdown.split(/\s+/).length;
const minutes = Math.ceil(words / (options.wordsPerMinute || 200));
return `*${minutes} min read*\n\n${markdown}`;
}
};
}
Auto-generate Sitemap
// plugins/sitemap.js
import fs from 'fs/promises';
import path from 'path';
export default function sitemap(options = {}) {
return {
name: 'sitemap',
async onPostBuild(buildData) {
const urls = buildData.posts.map(post =>
`${options.baseUrl || 'https://example.com'}/${post.slug}.html`
);
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(url => ` <url><loc>${url}</loc></url>`).join('\n')}
</urlset>`;
await fs.writeFile(
path.join(buildData.config.outputDir, 'sitemap.xml'),
sitemapXml
);
}
};
}
Plugin Data
Plugins receive data through hook parameters:
onMarkdownParse(markdown, frontmatter)
markdown
- Raw markdown contentfrontmatter
- Post metadata object
onRenderHTML(html, frontmatter)
html
- Generated HTML stringfrontmatter
- Post metadata object
onPostBuild(buildData)
buildData.config
- Zeno configurationbuildData.posts
- Array of all postsbuildData.outputDir
- Build output directory
Plugin Options
Pass options from config:
{
"plugins": [
{
"name": "my-plugin",
"options": {
"setting1": "value",
"setting2": true,
"setting3": 42
}
}
]
}
Plugin Tips
- Keep it simple - One plugin, one feature
- Use options - Make plugins configurable
- Handle errors - Wrap in try/catch blocks
- Test thoroughly - Check with different content
- Export default - Always use
export default