Hook System
Hooks let you run code at specific points in Dune's lifecycle — when a page loads, before rendering, after cache events, and more.
Available hooks
Startup hooks
| Hook |
When it fires |
Use case |
onConfigLoaded |
Config fully merged and validated |
Modify config, set up external services |
onStorageReady |
Storage adapter initialized |
Verify connectivity, warm caches |
onContentIndexReady |
Content index built/loaded |
Build search index, generate sitemap |
Request lifecycle hooks
| Hook |
When it fires |
Use case |
onRequest |
Incoming request (before routing) |
Analytics, auth, rate limiting |
onRouteResolved |
Route matched to a page |
URL rewriting, A/B testing |
onPageLoaded |
Full page object loaded |
Content transformation |
onCollectionResolved |
Collection query executed |
Modify collection results |
onBeforeRender |
Before JSX rendering |
Inject data, modify props |
onAfterRender |
After rendering (HTML available) |
Post-processing, minification |
onResponse |
Before response sent |
Headers, compression |
Content processing hooks
| Hook |
When it fires |
Use case |
onMarkdownProcess |
Before markdown → HTML |
Custom syntax, shortcodes |
onMarkdownProcessed |
After markdown → HTML |
HTML post-processing |
onMediaDiscovered |
Media files found for page |
Image optimization triggers |
Cache hooks
| Hook |
When it fires |
Use case |
onCacheHit |
Serving from cache |
Analytics, cache headers |
onCacheMiss |
Cache miss, will process |
Performance monitoring |
onCacheInvalidate |
Cache entry invalidated |
CDN purging |
API hooks
| Hook |
When it fires |
Use case |
onApiRequest |
Before API request is handled |
Auth, rate limiting, request logging |
onApiResponse |
After API response is built |
Response transformation, headers |
Engine lifecycle hooks
| Hook |
When it fires |
Use case |
onRebuild |
After a successful engine.rebuild() |
Clear downstream caches, notify search index |
onThemeSwitch |
When the active theme changes |
Purge theme-specific caches, notify CDN |
Content mutation hooks
Fired by the admin panel after CRUD operations. Useful for triggering external systems (CDN purges, search re-indexing, notifications) without using outbound webhooks.
| Hook |
When it fires |
Use case |
onPageCreate |
New page created and index rebuilt |
Notify external search, invalidate CDN |
onPageUpdate |
Page saved and index rebuilt |
Notify external search, invalidate CDN |
onPageDelete |
Page deleted and index rebuilt |
Remove from external search, purge CDN |
onWorkflowChange |
Page workflow status changed |
Trigger review notifications, update CMS |
Registering hooks
// plugins/my-hooks.ts
import type { DunePlugin } from "dune/types";
export default {
name: "my-hooks",
version: "1.0.0",
hooks: {
onRequest: async ({ data, config }) => {
// Log every request
console.log(`[${new Date().toISOString()}] ${data.req.method} ${data.req.url}`);
},
onMarkdownProcess: async ({ data, setData }) => {
// Replace custom shortcodes before markdown processing
const modified = data.raw.replace(
/\{\{youtube\s+(\w+)\}\}/g,
'<iframe src="https://youtube.com/embed/$1"></iframe>',
);
setData({ ...data, raw: modified });
},
onAfterRender: async ({ data }) => {
// Add reading time to rendered HTML
const wordCount = data.html.split(/\s+/).length;
const minutes = Math.ceil(wordCount / 200);
data.html = data.html.replace(
"</article>",
`<p class="reading-time">${minutes} min read</p></article>`,
);
},
},
} satisfies DunePlugin;
Hook context
Every hook handler receives a HookContext object:
interface HookContext<T> {
event: HookEvent; // which hook is firing
data: T; // event-specific data
config: DuneConfig; // full merged config
storage: StorageAdapter; // storage access
stopPropagation(): void; // stop further hooks for this event
setData(data: T): void; // replace event data
}
stopPropagation() prevents subsequent hooks from running for this event. Use it when a hook fully handles something (like a custom 404 page or an auth redirect).
setData() replaces the data flowing through the hook chain. The next hook receives the modified data.
Event data shapes
The data field in HookContext is typed per event. Here is what each hook receives:
Startup hooks (fired automatically by the engine)
| Hook |
data type |
Description |
onConfigLoaded |
DuneConfig |
The fully merged config object |
onStorageReady |
StorageAdapter |
The initialized storage adapter |
onContentIndexReady |
PageIndex[] |
All indexed pages |
Request lifecycle hooks (intended for custom server integrations)
| Hook |
data shape |
Description |
onRequest |
{ req: Request } |
Incoming HTTP request before routing |
onRouteResolved |
{ req: Request, page: PageIndex } |
Route matched to a page |
onPageLoaded |
{ req: Request, page: Page } |
Full page object ready |
onCollectionResolved |
{ req: Request, collection: Collection } |
Collection query result |
onBeforeRender |
{ req: Request, page: Page, props: Record<string, unknown> } |
Before JSX render |
onAfterRender |
{ req: Request, html: string } |
After render, HTML available |
onResponse |
{ req: Request, response: Response } |
Before response is sent |
Content processing hooks
| Hook |
data shape |
Description |
onMarkdownProcess |
{ raw: string, page: PageIndex } |
Raw Markdown before processing |
onMarkdownProcessed |
{ html: string, page: PageIndex } |
Rendered HTML after processing |
onMediaDiscovered |
{ media: MediaFile[], page: PageIndex } |
Media files found for a page |
Cache hooks
| Hook |
data shape |
Description |
onCacheHit |
{ key: string, value: unknown } |
Cache entry found |
onCacheMiss |
{ key: string } |
Cache entry not found |
onCacheInvalidate |
{ key: string } |
Cache entry removed |
API hooks
| Hook |
data shape |
Description |
onApiRequest |
{ req: Request } |
Before API request is handled |
onApiResponse |
{ req: Request, response: unknown } |
After API response is built |
Engine lifecycle hooks
| Hook |
data shape |
Description |
onRebuild |
{} |
Fired at the end of a successful engine.rebuild() |
onThemeSwitch |
{ from: string, to: string } |
Fired when the active theme changes (old and new theme names) |
Content mutation hooks
| Hook |
data shape |
Description |
onPageCreate |
{ sourcePath: string, title: string } |
Fired after a new page is created via the admin panel |
onPageUpdate |
{ sourcePath: string, title: string } |
Fired after a page is saved via the admin panel |
onPageDelete |
{ sourcePath: string } |
Fired after a page is deleted via the admin panel |
onWorkflowChange |
{ sourcePath: string, from: WorkflowStatus, to: WorkflowStatus } |
Fired after a page's workflow status changes |
WorkflowStatus is "draft" | "in_review" | "published" | "archived".
These same events also trigger outbound webhooks when admin.webhooks is configured — hooks and webhooks fire in parallel.
Note: The startup hooks (onConfigLoaded, onStorageReady, onContentIndexReady) and engine lifecycle hooks (onRebuild, onThemeSwitch) are fired automatically by Dune. The request and API hooks can also be fired by custom server code using hooks.fire(event, data) when integrating Dune into a custom server.
Hook execution order
Hooks fire in the order they're registered. If multiple plugins register the same hook, they run sequentially. Each hook sees the data as modified by previous hooks.