Advanced
Plugin Development
Creating custom plugins to extend Manic functionality.
Plugin Development
TL;DR
Learn how to create custom plugins to extend Manic with custom functionality, static files, and server hooks.
What It Is
Plugins extend Manic's core functionality:
| Plugin Type | Purpose | Hooks |
|---|---|---|
| Build plugins | Modify build output | build() |
| Server plugins | Add server routes | configureServer() |
| Static plugins | Emit static files | staticFiles() |
Prerequisites
- Plugins Guide - Using plugins
- Config Reference - Configuration
- Advanced: Internals - Understanding the framework
Quick Start
Create a Simple Plugin
// my-plugin.ts
import { } from 'manicjs/config';
export function (?: { ?: string }) {
return ({
: 'my-plugin',
(: { : (: string) => void }) {
.('Building with my plugin!');
.('<meta name="my-plugin" content="true">');
},
});
}Use in Config
// manic.config.ts
import { } from 'manicjs/config';
function (?: { ?: string }) {
return { : 'my-plugin' as };
}
export default ({
: [
({ : 'my-app' }),
],
});Plugin Interface
ManicPlugin
interface ManicPlugin {
name: string;
// Build-time hooks
build?: (ctx: ManicBuildPluginContext) => void | Promise<void>;
// Dev server hooks
configureServer?: (ctx: ManicServerPluginContext) => void | Promise<void>;
// Static file generation
staticFiles?: StaticFile[];
}
interface ManicBuildPluginContext {
config: ManicConfig;
pageRoutes: { path: string; filePath: string; dynamic: boolean }[];
apiRoutes: { mountPath: string; filePath: string }[];
prod: boolean;
cwd: string;
dist: string;
emitClientFile(path: string, content: string | Uint8Array): Promise<void>;
injectHtml(tags: string): void;
}
interface ManicServerPluginContext {
config: ManicConfig;
pageRoutes: { path: string; filePath: string; dynamic: boolean }[];
apiRoutes: { mountPath: string; filePath: string }[];
prod: boolean;
cwd: string;
dist: string;
addRoute(path: string, handler: (req: Request) => Response | Promise<Response>): void;
addLinkHeader(value: string): void;
injectHtml(tags: string): void;
}
interface StaticFile {
path: string;
content: string | ((ctx: any) => string);
contentType?: string;
}Build Hook
Using build() Hook
declare function (: {
: string;
?: (: {
: (: string) => void;
: (: string, : string | ) => <void>;
}) => void;
}): any;
({
: 'my-build-plugin',
() {
// Inject into HTML
.('<script src="/analytics.js"></script>');
// Emit static file
.('/analytics.js', 'console.log("tracked")');
},
});Build Context API
interface ManicBuildPluginContext {
// All discovered page routes
pageRoutes: { path: string; filePath: string; dynamic: boolean }[];
// All discovered API routes
apiRoutes: { mountPath: string; filePath: string }[];
// Output directory
dist: string;
// Emit a file to dist/client/
emitClientFile(path: string, content: string | Uint8Array): Promise<void>;
// Inject into <head>
injectHtml(tags: string): void;
}Server Hook
Using configureServer() Hook
declare function (: { : string; ?: (: { : (: string, : (: any) => any) => void; : (: string) => void; : (: string) => void }) => void }): any;
({
: 'my-server-plugin',
() {
// Add custom route
.('/health', (: any) => .text('OK'));
// Add Link header for preloading
.('</style.css>; rel=preload; as=style');
// Inject into HTML
.('<meta name="my-plugin" content="true">');
},
});Server Context API
interface ManicServerPluginContext {
// Hono app instance
addRoute(path: string, handler: (req: Request) => Response | Promise<Response>): void;
// Add Link header
addLinkHeader(value: string): void;
// Inject into HTML
injectHtml(tags: string): void;
}Static Files
Using staticFiles
declare function (: { : string; ?: { : string; : string | ((: any) => string); ?: string }[] }): any;
({
: 'my-static-plugin',
: [
{
: '/manifest.json',
: .({ : 'my-app' }),
: 'application/json',
},
{
: '/custom.js',
: 'console.log("hello")',
},
],
});Examples
Example 1: Analytics Plugin
declare function (: { : string; ?: (: { : (: string) => void }) => void }): any;
declare function (: { : any[] }): any;
export function (: string) {
return ({
: 'analytics',
() {
const = `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${}');
`;
.(`
<script async src="https://www.googletagmanager.com/gtag/js?id=${}"></script>
<script>${}</script>
`);
},
});
}
// Usage
export default ({
: [
('G-XXXXXXXXXX'),
],
});Example 2: Sitemap Generator
declare function (: { : string; ?: (: { : { : string }[]; : (: string) => void; : (: string, : string) => void }) => void }): any;
export function () {
return ({
: 'sitemap',
() {
const = ..( => {
const = `https://example.com${.}`;
return `<url><loc>${}</loc></url>`;
}).('\n');
const = `<?xml version="1.0"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${}
</urlset>`;
.('/sitemap.xml', );
},
});
}Example 3: SEO Plugin
declare function (: { : string; ?: (: { : (: string) => void }) => void }): any;
export function (: { : string; : string }) {
return ({
: 'seo',
() {
.(`
<title>${.}</title>
<meta name="description" content="${.}">
<meta property="og:title" content="${.}">
<meta property="og:description" content="${.}">
`);
},
});
}Advanced Patterns
Pattern 1: Conditional Features
declare function (: { : string; ?: (: { : (: string) => void }) => void }): any;
({
: 'conditional',
() {
if (.. === 'production') {
// Production only
.('<meta name="robots" content="index">');
}
},
});Pattern 2: Multiple Files
declare function (: { : string; ?: { : string; : (: { : { : string }[] }) => string }[] }): any;
({
: 'multi-file',
: [
{
: '/data/v1.json',
: () => .({ : . }),
},
],
});Common Issues
Issue 1: Plugin Not Loading
Solution:
declare function (: { : any[] }): any;
declare function (): { : string };
export default ({
: [()], // Must be in plugins array
});Issue 2: Route Conflicts
Solution:
declare const : { : (: string, : unknown) => void };
declare const : unknown;
.('/my-plugin/api', );Best Practices
Use createPlugin helper for consistency.
Avoid modifying the same file multiple times in a single build pass to prevent race conditions.
Document plugin options clearly.
See also: