Skip to main content
The registerPlugin function is the primary way to extend Scully’s functionality by registering custom plugins. It allows you to hook into various stages of Scully’s build process.

Signature

function registerPlugin<T extends keyof PluginFuncs>(
  type: T,
  name: string | symbol,
  plugin: PluginFuncs[T],
  pluginOptions?: ConfigValidator | number | string[],
  options?: RegisterOptions
): void

Parameters

type
PluginTypes
required
The type of plugin to register. Must be one of:
  • 'router' - Discovers routes in your application
  • 'render' or 'postProcessByHtml' - Processes HTML as a string
  • 'postProcessByDom' - Processes HTML using JSDOM
  • 'fileHandler' - Handles specific file extensions
  • 'routeProcess' - Processes routes before rendering
  • 'allDone' - Runs after all routes are rendered
  • 'beforeAll' - Runs before route discovery
  • 'routeDiscoveryDone' - Runs after route discovery completes
  • 'enterprise' - Enterprise-level plugin hooks
  • 'scullySystem' - Internal system plugins
name
string | symbol
required
Unique identifier for the plugin. Use a string constant (e.g., 'myPlugin' as const) instead of symbols.
plugin
PluginFuncs[T]
required
The plugin implementation function. The signature varies based on the plugin type.
pluginOptions
ConfigValidator | number | string[]
Additional options based on plugin type:
  • For 'router' plugins: A ConfigValidator function that validates route configuration
  • For 'fileHandler' plugins: An array of alternate file extensions (e.g., ['.md', '.markdown'])
  • For 'beforeAll' or 'routeProcess' plugins: A priority number (default: 100)
options
RegisterOptions
Additional registration options:
  • replaceExistingPlugin: Set to true to replace an existing plugin with the same name (default: false)

Plugin Types

Router Plugins

Router plugins discover and generate routes for your application.
import { registerPlugin, RoutePlugin } from '@scullyio/scully';

const jsonPlugin: RoutePlugin = async (route, config) => {
  // Fetch data from an API
  const data = await fetch(config.url).then(r => r.json());
  
  // Return an array of HandledRoute objects
  return data.map(item => ({
    route: `/products/${item.id}`,
    type: 'json',
    data: item
  }));
};

// Validator function to check the config
const validator = (config) => {
  const errors = [];
  if (!config.url) {
    errors.push('url is required');
  }
  return errors;
};

registerPlugin('router', 'json', jsonPlugin, validator);

Render Plugins

Render plugins modify the HTML after it has been rendered.
import { registerPlugin, postProcessByHtmlPlugin } from '@scullyio/scully';

const customRenderPlugin: postProcessByHtmlPlugin = async (html, route) => {
  // Modify the HTML string
  return html.replace(/<h1>/g, '<h1 class="custom-heading">');
};

registerPlugin('postProcessByHtml', 'customRender', customRenderPlugin);

DOM Plugins

DOM plugins work with the JSDOM representation for more complex manipulations.
import { registerPlugin, postProcessByDomPlugin } from '@scullyio/scully';

const addMetaTagsPlugin: postProcessByDomPlugin = async (dom, route) => {
  const document = dom.window.document;
  
  // Add a meta tag
  const meta = document.createElement('meta');
  meta.setAttribute('name', 'description');
  meta.setAttribute('content', route.data?.description || 'Default description');
  document.head.appendChild(meta);
  
  return dom;
};

registerPlugin('postProcessByDom', 'addMetaTags', addMetaTagsPlugin);

File Handler Plugins

File handler plugins process content files with specific extensions.
import { registerPlugin, FilePlugin } from '@scullyio/scully';

const asciidocPlugin: FilePlugin = async (html, route) => {
  // Process AsciiDoc content
  const asciidoctor = require('asciidoctor')();
  return asciidoctor.convert(html);
};

registerPlugin(
  'fileHandler',
  'asciidoc',
  asciidocPlugin,
  ['.adoc', '.asciidoc']  // File extensions this plugin handles
);

Route Process Plugins

Route process plugins modify the route list before rendering.
import { registerPlugin, RouteProcess } from '@scullyio/scully';

const filterDrafts: RouteProcess = async (routes) => {
  // Filter out draft posts
  return routes.filter(route => !route.data?.draft);
};

registerPlugin(
  'routeProcess',
  'filterDrafts',
  filterDrafts,
  50  // Priority: lower numbers run first
);

Lifecycle Plugins

Plugins that run at specific lifecycle points.
import { registerPlugin, BeforeAllPlugin, AllDonePlugin } from '@scullyio/scully';

const setupPlugin: BeforeAllPlugin = async () => {
  console.log('Setting up before build...');
  // Return false to abort the build
  return true;
};

const cleanupPlugin: AllDonePlugin = async (routes) => {
  console.log(`Rendered ${routes.length} routes`);
  // Perform cleanup tasks
};

registerPlugin('beforeAll', 'setup', setupPlugin, 100);
registerPlugin('allDone', 'cleanup', cleanupPlugin);

Error Handling

If a plugin with the same name already exists, registerPlugin will throw an error unless replaceExistingPlugin: true is specified in options.
// This will throw an error if 'myPlugin' already exists
registerPlugin('router', 'myPlugin', myPluginV1);

// This will replace the existing plugin
registerPlugin(
  'router',
  'myPlugin',
  myPluginV2,
  validator,
  { replaceExistingPlugin: true }
);

Best Practices

While symbols are supported, they’re deprecated. Use string constants with as const for better debugging:
const MY_PLUGIN = 'myPlugin' as const;
registerPlugin('router', MY_PLUGIN, myPlugin);
Router plugins should include a validator function to check configuration:
const validator = (config) => {
  const errors = [];
  if (!config.requiredField) {
    errors.push('requiredField is required');
  }
  return errors;
};
Plugin functions should catch and handle errors to prevent build failures:
const safePlugin = async (html, route) => {
  try {
    return await transformHtml(html);
  } catch (error) {
    console.error('Plugin error:', error);
    return html; // Return original HTML on error
  }
};
For beforeAll and routeProcess plugins, set priorities based on execution order needs:
  • Lower numbers (e.g., 50) run first
  • Default priority is 100
  • Higher numbers (e.g., 150) run last

Source Reference

Source: libs/scully/src/lib/pluginManagement/pluginRepository.ts:46

findPlugin

Retrieve a registered plugin by name

Plugin System

Learn more about Scully’s plugin architecture

Creating Plugins

Step-by-step guide to creating custom plugins

Plugin Types

Detailed documentation for each plugin type

Build docs developers (and LLMs) love