Skip to main content

Plugin System

The Formitiva plugin system enables modular extension of the framework through a centralized registry. Plugins can register field components, validation handlers, submission handlers, and lifecycle hooks while ensuring controlled conflict resolution and safe unregistration.


Overview

Plugins are installed using a single entry point:

registerPlugin(plugin, options)

A plugin may contribute:

  • Field components
  • Per-field custom validators
  • Type-level validators
  • Form-level validators
  • Submission handlers
  • Lifecycle hooks (setup, cleanup)

The registry:

  • Detects and resolves registration conflicts
  • Tracks ownership of registered items
  • Supports controlled overrides
  • Allows safe unregistration

Plugin Interface

A plugin implements the FormitivaPlugin interface:

interface FormitivaPlugin {
name: string;
version: string;
description?: string;

components?: Record<string, React.ComponentType>;

fieldCustomValidators?: Record<
string,
Record<string, FieldCustomValidationHandler>
>;

fieldTypeValidators?: Record<
string,
FieldTypeValidationHandler
>;

formValidators?: Record<
string,
FormValidationHandler
>;

submissionHandlers?: Record<
string,
FormSubmissionHandler
>;

setup?: () => void;
cleanup?: () => void;
}

Plugin Properties

name

Unique plugin identifier. Must not collide with other registered plugins.

version

Plugin version string.

components

Registers field components by field type.

components: {
point2d: Point2DInput
}

fieldCustomValidators

Registers per-field custom validators grouped by category.

  • Category is typically the form definition name.
  • Structure: category → handlerName → handler
fieldCustomValidators: {
point2d: {
nonNegativePoint: handler
}
}

fieldTypeValidators

Registers type-level validators by field type.

  • Executed before per-field custom validation.
  • Applied globally to all fields of that type.
fieldTypeValidators: {
point2d: typeValidator
}

formValidators

Registers named form-level validation handlers.

formValidators: {
'point2d:regionValidator': regionValidator
}

submissionHandlers

Registers named submission handlers.

submissionHandlers: {
'point2d:alertSubmission': handler
}

setup

Optional hook invoked after successful registration.

cleanup

Optional hook invoked during unregistration.


Example Plugin

export const PointPlugin: FormitivaPlugin = {
name: 'point_plugin',
version: '0.1.0',

components: {
point2d: Point2DInput,
},

fieldCustomValidators: {
point2d: {
nonNegativePoint,
},
},

formValidators: {
'point2d:regionValidator': regionValidator,
},

submissionHandlers: {
'point2d:alertSubmission': alertSubmissionHandler,
},

setup: () => {
console.log('PointPlugin registered');
},

cleanup: () => {
console.log('PointPlugin unregistered');
},
};

Registering a Plugin

import { registerPlugin } from '@formitiva/react';
import { PointPlugin } from './PointPlugin';

registerPlugin(PointPlugin, {
conflictResolution: 'warn',
});

Registration Flow

When registerPlugin is called, the registry:

  1. Detects conflicts with existing registrations.
  2. Resolves conflicts using the configured strategy.
  3. Registers items in their respective registries.
  4. Calls plugin.setup() (if provided).
  5. Records ownership metadata.

Conflict Resolution

You can control conflict behavior using:

registerPlugin(plugin, {
conflictResolution: 'error' | 'warn' | 'override' | 'skip'
});

Strategies

StrategyBehavior
errorThrows immediately (default)
warnLogs warning and skips conflicting items
overrideReplaces existing registration
skipSilently skips conflicting items

You may also provide a custom resolver:

registerPlugin(plugin, {
onConflict(conflict) {
return 'override';
}
});

Validator Categories and Lookup

Per-Field Custom Validators

Custom validators are grouped by category (usually the form definition name).

When a field specifies:

validationHandlerName: "nonNegativePoint"

The system resolves the handler using:

  • The current form definition name as category
  • The provided handler name

To reference a specific category explicitly:

validationHandlerName: ["point2d", "nonNegativePoint"]

Type-Level Validators

Type-level validators:

  • Are registered by field type
  • Execute before per-field custom validation
  • Apply globally to all fields of that type

Unregistering a Plugin

unregisterPlugin(pluginName, removeRegistrations);

If removeRegistrations is true:

  • All components, validators, and handlers owned by the plugin are removed.
  • The cleanup() hook is invoked.

Ownership tracking ensures only items registered by the plugin are removed.


Ownership Model

The registry maintains internal ownership mappings for:

  • Components
  • Field validators
  • Form validators
  • Submission handlers

This enables:

  • Safe unregistration
  • Controlled overrides
  • Deterministic conflict handling

Best Practices

  • Keep plugins modular and feature-focused.
  • Namespace validator and handler names (e.g., point2d:regionValidator).
  • Register per-field validators under the relevant form definition.
  • Use setup and cleanup for runtime initialization and teardown.
  • Prefer conflictResolution: 'warn' during development.