Skip to main content

Plugins API

import { registerBasicPlugins, registerAsyncPlugins } from "formwright/plugins";

registerBasicPlugins()

Returns built-in operator and effect plugins. Always include this in your plugin array.

const runtime = createFormRuntime({
form,
plugins: registerBasicPlugins(),
});

Included operator plugins

PluginOperator typeBehavior
eqOperatorPlugineqStrict equality
neqOperatorPluginneqStrict inequality
andOperatorPluginandAll sub-expressions true
orOperatorPluginorAny sub-expression true
notOperatorPluginnotNegates expression
existsOperatorPluginexistsValue is non-empty
gtOperatorPlugingtGreater than
gteOperatorPlugingteGreater than or equal
ltOperatorPluginltLess than
lteOperatorPluginlteLess than or equal
inOperatorPlugininValue is in array

Included effect plugins

PluginEffect typeBehavior
showEffectPluginshowSet visible: true
hideEffectPluginhideSet visible: false
enableEffectPluginenableSet disabled: false
disableEffectPlugindisableSet disabled: true
requireEffectPluginrequireSet required state
setValueEffectPluginsetValuePush value mutation
clearValueEffectPluginclearValuePush undefined value mutation
setLayoutPropEffectPluginsetLayoutPropPatch layout node state

registerAsyncPlugins(options?)

Returns static and remote datasource plugins. Required when any field uses dataSource.

const runtime = createFormRuntime({
form,
plugins: [
...registerBasicPlugins(),
...registerAsyncPlugins(),
// or with a base URL for relative endpoints:
...registerAsyncPlugins({ baseUrl: "https://api.example.com" }),
],
});
OptionTypeDescription
baseUrlstringPrepended to relative datasource endpoints
fetchImpltypeof fetchCustom fetch implementation (for testing or SSR)

Plugin interfaces

All plugin interfaces are in formwright/core.

OperatorPlugin

interface OperatorPlugin {
kind: "operator";
identity: PluginIdentity;
operatorType: string;
evaluate: (input: OperatorEvaluateInput) => unknown;
}

interface OperatorEvaluateInput {
expression: Record<string, unknown>;
values: Record<string, unknown>;
context: RuntimeContext;
}

EffectPlugin

interface EffectPlugin {
kind: "effect";
identity: PluginIdentity;
effectType: string;
apply: (input: EffectApplyInput) => EffectApplyResult;
}

interface EffectApplyInput {
effect: RuleEffect;
values: Record<string, unknown>;
derivedState: DerivedStateSnapshot;
context: RuntimeContext;
}

interface EffectApplyResult {
fieldMutations?: Array<{ path: string; patch: Partial<DerivedFieldState> }>;
layoutMutations?: Array<{ id: string; patch: Partial<DerivedLayoutState> }>;
valueMutations?: Array<{ path: string; value: unknown }>;
}

FieldPlugin

interface FieldPlugin {
kind: "field";
identity: PluginIdentity;
fieldType: string;
normalize?: (input: FieldPluginNormalizeInput) => FieldPluginNormalizeOutput;
getDefaultValue?: (input: FieldDefaultValueInput) => unknown;
getValidationPlan?: (input: FieldValidationPlanInput) => ValidationPlanItem[];
getRendererKey?: (input: FieldRendererKeyInput) => string;
serialize?: (input: FieldSerializeInput) => unknown;
deserialize?: (input: FieldDeserializeInput) => unknown;
}

interface FieldPluginNormalizeOutput {
fieldType: string;
normalizedDataField: DataFieldDefinition;
normalizedUiField?: UiFieldNode;
}

ValidationPlanItem shape:

{ validatorType: string }

DataSourcePlugin

interface DataSourcePlugin {
kind: "datasource";
identity: PluginIdentity;
sourceType: string;
load: (input: DataSourceLoadInput) => Promise<DataSourceLoadResult>;
}

interface DataSourceLoadInput {
source: DataSourceDefinition;
dependsOnValues: Record<string, unknown>;
context: RuntimeContext;
}

interface DataSourceLoadResult {
options?: Array<{ value: string | number | boolean; label: string }>;
meta?: Record<string, unknown>;
}

ValidatorPlugin

interface ValidatorPlugin {
kind: "validator";
identity: PluginIdentity;
validatorType: string;
supports: (input: ValidatorSupportsInput) => boolean;
validate: (input: ValidatorRunInput) => ValidationResult;
}

PluginIdentity

interface PluginIdentity {
name: string;
version?: string;
}

Use a namespaced name to avoid collisions with built-in plugins:

identity: { name: "my-app/custom-operator", version: "1" }

Registering two plugins with the same identity.name throws DuplicatePluginError.

createRemoteDataSourcePlugin(options?)

Creates a datasource plugin for remote HTTP endpoints with custom fetch logic.

import { createRemoteDataSourcePlugin } from "formwright/plugins";

const plugin = createRemoteDataSourcePlugin({
fetchImpl: authenticatedFetch, // custom fetch with auth headers
});

createStaticDataSourcePlugin()

Creates a datasource plugin for static inline options. Already included in registerAsyncPlugins().

import { createStaticDataSourcePlugin } from "formwright/plugins";