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;

/**
* Field components keyed by field type name.
* React: React.ComponentType | Vue: defineComponent result
* Angular: Component class | Vanilla: FieldFactory function
*/
components?: Record<string, unknown>;

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​

The plugin structure is the same across all frameworks. Only the components value differs:

React / Vue / Angular / Vanilla JS​

// Import your framework component (examples shown for React; swap for other frameworks)
// React: import Point2DInput from './Point2DInput';
// Vue: import Point2DInput from './Point2DInput.vue';
// Angular: import { Point2DInputComponent } from './point2d-input.component';
// Vanilla: import { point2dFactory } from './point2dFactory';

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

components: {
point2d: Point2DInput, // or Point2DInputComponent (Angular) / point2dFactory (Vanilla)
},

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

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

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

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

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

Registering a Plugin​

The registerPlugin API is the same across all frameworks. Import from your framework package:

// React
import { registerPlugin } from '@formitiva/react';
// Vue
import { registerPlugin } from '@formitiva/vue';
// Angular
import { registerPlugin } from '@formitiva/angular';
// Vanilla JS
import { registerPlugin } from '@formitiva/vanilla';

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.