Plugin Hooks Reference
Detailed reference for every lifecycle hook available to tulip plugins.
Build pipeline
Hooks fire in a fixed order during the build. Each hook is optional; implement only what you need:
1. setup(config) — plugin loaded
2. beforeBuild(ctx) — build starts
3. transformMarkdown(content) — per page, before markdown → HTML
4. transformContext(ctx) — per page, before template render
5. head() — collect tags for HTML head
6. transformHtml(html) — per page, after template render
7. generatePages(ctx) — create new files
8. afterBuild(ctx) — build completeTypeScript interfaces
These are the core types used across all hooks:
Page
interface Page {
slug: string;
url: string;
frontmatter: Record<string, any>;
content: string;
}BuildContext
interface BuildContext {
pages: Page[];
site: {
name: string;
base_url: string;
description: string;
};
config: PluginConfig;
}HeadTag
interface HeadTag {
tag: string;
attrs: Record<string, string | boolean>;
}GeneratedPage
interface GeneratedPage {
path: string;
content: string;
}setup(config)
Called once when the plugin is loaded, before any build work begins. Use it to store configuration, initialize state, or validate config values.
setup(config: PluginConfig) {
if (!config.site_name) {
throw new Error("seo plugin requires site_name in config");
}
this.config = config;
}| Parameter | Type | Description |
|---|---|---|
config | PluginConfig | Merged config from plugin.toml defaults and tulip.toml overrides |
beforeBuild(ctx)
Called once after all plugins are loaded and before any pages are processed.
beforeBuild(ctx: BuildContext) {
ctx.pages = ctx.pages.filter(function(p) {
return !p.frontmatter.draft;
});
}transformMarkdown(content, page)
Called for each markdown page before it is converted to HTML. Return the modified markdown string.
transformMarkdown(content: string, page: Page): string {
let words = content.split(/\s+/).length;
let minutes = Math.ceil(words / 200);
return "*" + minutes + " min read*\n\n" + content;
}Returns: string — the transformed markdown.
transformContext(ctx, page)
Called for each page before template rendering. Receives the template context object and returns a modified version.
transformContext(ctx: object, page: Page): object {
ctx.year = new Date().getFullYear();
ctx.is_home = page.slug === "index";
return ctx;
}Returns: object — the modified template context.
head()
Called once to collect tags that should be injected into the HTML <head>. Return an array of HeadTag objects:
head(): HeadTag[] {
return [
{ tag: "script", attrs: { src: "https://cdn.example.com/script.js", defer: true } },
{ tag: "link", attrs: { rel: "stylesheet", href: "/css/extra.css" } },
];
}Each tag is rendered as HTML and injected before the closing </head> tag. Boolean attributes like defer are rendered without a value.
transformHtml(html, page)
Called for each page after template rendering. Receives the final HTML string and returns a modified version.
transformHtml(html: string, page: Page): string {
let tag = '<meta property="og:title" content="' + page.frontmatter.title + '">';
return html.replace("</head>", tag + "\n</head>");
}Returns: string — the transformed HTML.
generatePages(ctx)
Called once after all pages are rendered. Return an array of GeneratedPage objects to create additional output files like RSS feeds, sitemaps, or JSON APIs.
generatePages(ctx: BuildContext): GeneratedPage[] {
let urls = "";
for (let i = 0; i < ctx.pages.length; i++) {
let page = ctx.pages[i];
urls += "<url><loc>" + ctx.site.base_url + page.url + "</loc></url>\n";
}
let sitemap = '<?xml version="1.0" encoding="UTF-8"?>\n';
sitemap += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
sitemap += urls;
sitemap += "</urlset>";
return [{ path: "sitemap.xml", content: sitemap }];
}Returns: GeneratedPage[] — each entry has a path (relative to the build target) and content (file contents).
afterBuild(ctx)
Called once after the build is complete. Use it for cleanup, logging, or post-build tasks.
afterBuild(ctx: BuildContext) {
let count = ctx.pages.length;
console.log("Built " + count + " pages");
}