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 TypePurposeHooks
Build pluginsModify build outputbuild()
Server pluginsAdd server routesconfigureServer()
Static pluginsEmit static filesstaticFiles()

Prerequisites


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:

On this page