Skip to content

Plugins

Kanun supports installable plugins so framework-specific or optional validation behavior can live outside the core package.

This keeps the base validator small while allowing packages to add new rules, messages, type inference, output transformations, request-context helpers, and framework adapters.

Installing A Plugin

Install the core package and the plugin you need:

bash
npm install kanun @kanun-hq/plugin-file
bash
pnpm add kanun @kanun-hq/plugin-file
bash
yarn add kanun @kanun-hq/plugin-file

Register the plugin once during app startup:

ts
import { Validator } from 'kanun';
import { fileValidatorPlugin } from '@kanun-hq/plugin-file';

Validator.use(fileValidatorPlugin);

Available Plugins

  • File Validation: upload validation rules, file size semantics, wildcard helpers, and Express/Fastify/Hono/h3 adapters.
  • Phone Validation: phone number validation, parsed PhoneNumber output, formatting helpers, and Arkormˣ-style casts.

Validator Context

Plugins can read extra runtime state through validator context.

ts
const validator = Validator.make(data, rules).withContext({
  request,
  currentUser,
});

Context is useful when a rule depends on request-bound information such as uploaded files, authenticated users, or framework services.

If you want to register request-scoped context once in middleware and let validators created later pick it up automatically, use the static API:

ts
import { Validator } from 'kanun';

Validator.useContext({
  request,
  currentUser,
});

Writing Your Own Plugin

Kanun exposes a plugin API for packages that need to register rules or extend runtime behavior.

ts
import { definePlugin } from 'kanun';

export const examplePlugin = definePlugin({
  name: 'example-plugin',
  install: ({ registerRule, extendTranslations, onValidationError, onValidationSuccess }) => {
    registerRule('starts_with_kanun', (value) => {
      return typeof value === 'string' && value.startsWith('kanun');
    });

    extendTranslations({
      en: {
        starts_with_kanun: 'The :attribute must start with kanun.',
      },
    });

    onValidationSuccess((validator) => {
      // Runs after a validator completes successfully.
    });

    onValidationError((validator) => {
      // Runs after a validator fails and the error bag has been populated.
      validator.errors().all();
    });
  },
});

Validation Lifecycle Hooks

Plugins can register lifecycle hooks that run after each validator execution.

ts
export const auditPlugin = definePlugin({
  name: 'audit-plugin',
  install: ({ onValidationSuccess, onValidationError }) => {
    onValidationSuccess((validator) => {
      const data = validator.validatedData();
      // send successful validation metadata to your integration
    });

    onValidationError((validator) => {
      const errors = validator.errors().all();
      // send failed validation metadata to your integration
    });
  },
});

onValidationError(...) runs after validation fails and the validator's MessageBag has been populated. onValidationSuccess(...) runs after validation passes, so plugins can safely read validatedData().

Add Rule Autocomplete For Plugin Users

If your plugin adds custom rules, you can augment Kanun's rule autocomplete so array-style rule definitions suggest your rule names in TypeScript.

ts
declare module 'kanun' {
  interface ValidationRuleAutocompleteMap {
    starts_with_kanun: 'plain';
    kanun_format: 'paramable';
  }
}

Use 'plain' for rules without parameters and 'paramable' for rules used like rule:value.

That makes plugin rules show up in array syntax such as:

ts
const rules = {
  name: ['required', 'starts_with_kanun', 'kanun_format:strict'],
};

This only affects TypeScript autocomplete and typing. You still need to register the runtime rule implementation inside your plugin with registerRule(...).

If a plugin needs runtime state, document the expected shape of .withContext() and provide adapters when framework integration is common.