Guides

Create & Publish a Plugin

Scaffold, build, test, and publish a Frontguard plugin to the npm registry.

Create & Publish a Plugin

Frontguard plugins extend the pipeline via lifecycle hooks. Anyone can publish a plugin to npm and have others install it with a single command. This guide walks through scaffolding, building, testing, and publishing one.

Already know the hook system? Jump to Publishing. For the full hook reference, see Custom Plugins.

Naming convention

Frontguard discovers and installs plugins by a naming convention:

frontguard-plugin-<name>            # unscoped
@your-scope/frontguard-plugin-<name> # scoped

The CLI resolves short names automatically — frontguard plugin install slack installs frontguard-plugin-slack.

Scaffold a new plugin

The fastest way to start is the official scaffolder:

npm create frontguard-plugin@latest my-plugin
# or
npx create-frontguard-plugin slack --description "Post results to Slack"

This generates a complete, tested project:

frontguard-plugin-slack/
├── package.json        # correct name, keywords, peerDeps
├── tsconfig.json
├── vitest.config.ts
├── src/index.ts        # plugin factory with setup + afterRun hooks
├── test/index.test.ts  # passing tests
└── README.md

Then:

cd frontguard-plugin-slack
npm install
npm test       # tests pass out of the box
npm run build

Anatomy of a plugin

A plugin is an object with a unique name and one or more optional hooks. The scaffold exports a factory so users can pass options:

import type { FrontguardPlugin } from '@frontguard/cli';

export interface SlackOptions {
  webhookUrl: string;
}

export function createSlackPlugin(options: SlackOptions): FrontguardPlugin {
  return {
    name: 'slack',
    setup() {
      if (!options.webhookUrl) throw new Error('slack: webhookUrl is required');
    },
    async afterRun(result) {
      const { regressions } = result.summary;
      if (regressions > 0) {
        await fetch(options.webhookUrl, {
          method: 'POST',
          body: JSON.stringify({ text: `⚠️ ${regressions} visual regression(s) detected` }),
        });
      }
    },
  };
}

export default createSlackPlugin;

Supported export shapes

The loader accepts any of these (in priority order):

  1. export default a factory function (config) => plugin
  2. export default a plugin object
  3. a named export const plugin (object or factory)
  4. the module itself being a plugin object

Using a plugin

Users install and reference it in their config:

frontguard plugin install slack
// frontguard.config.ts
import createSlackPlugin from 'frontguard-plugin-slack';

export default {
  baseUrl: 'http://localhost:3000',
  plugins: [createSlackPlugin({ webhookUrl: process.env.SLACK_WEBHOOK })],
};

Managing installed plugins

frontguard plugin list          # show installed frontguard-plugin-* packages
frontguard plugin install slack # add one
frontguard plugin uninstall slack

plugin list scans node_modules for packages matching the naming convention, including scoped packages, and prints their version + description.

Publishing

  1. Make sure package.json name follows the convention and keywords include frontguard-plugin.

  2. Build and test:

    npm run build && npm test
  3. Publish:

    npm publish --access public

Add the frontguard-plugin keyword so your plugin is discoverable via npm search.

Version compatibility

Declare @frontguard/cli as a peer dependency (the scaffold does this) so your plugin shares the host's Frontguard version rather than bundling its own. The loader surfaces a clear error if a plugin can't be imported, pointing users to frontguard plugin install.

On this page